Introduction to Model-View-Presenter

By | April 21, 2015

Introduction

This is a simple MVP implementation in an ASP.NET application. I used this pattern a lot during 2007-2011 time period before ASP.NET MVC in Microsoft world started to gain popularity. MVP separates visual display logic from data procurement logic. In Model-View-Presenter, View is solely responsible for display of data. Presenter is concerned about getting the relevant data for view. View will raise requests to serve data and Presenter honors those requests and Model represents a repository.

This pattern allows us to separate the responsibilities of application so that application code is :-

  • Code is testable using test and mocking frameworks
  • Application is more maintainable as it provides separation of concern
  • Application is more extensible

Presenter and View know each other only through the contract which is implemented by View. Generally a contract is an Interface, a presenter can serve one or more views. Each view implements an interface. Interface instantiates presenter by performing a constructor injection. This allows presenter to understand which contract to be served for the calling view.

Presenter’s talks to the model (Database) through Business APIs to pull required data as per the requested contract. The business entity might be different than data format requested by view. Presenter formats that data in the required format to serve views contract. The Presenter functionality can be tested by mocking up views that raises request and is served by Presenter.

MVP

View: A view is any form or window that represents UI of the application.

Presenter: The presenter is an entity that presents data to the view as per the requested contract, It also formats that data from business model to the requested view contract.

Model: The model is the actual data that the Presenter will request and gets displayed in the View. The Model is responsible for obtaining the data.

The sample code is available for download at Microsoft sample website: –

https://code.msdn.microsoft.com/Model-View-Presenter-clean-d33ee8d1

Person search sample code

The sample code was written in Visual Studio 2010. You can alternatively add projects in sample code to an earlier version of Visual Studio solution to make it run in older version of visual studio. It works without any issue if upgraded to Visual Studio 2013 or 2015. It is built using 3.5 .NET framework. Framework 2.0 will also work if you change the sample code accordingly.

Sample project structure

Sample code structureThere are four projects in this solution.

1. Service

2. Presenter

3. EModel

4. Views

The Service project provides a mock implementation of backend. In MVP it is playing role of Model. Service provides an API GetPersons for a given criteria. This returns a collection of Person type.

The EModel project represents Enterprise model classes (Service Types). These are light weight objects to be consumed by views. There can be several service type objects for a given business or domain object. The service serves these lightweight types to the external world. Business or Domain types are not exposed by service tier.

Business Types are not shown in the sample. If available then a translation can be added from Business types to EModel Types and vice versa. The business or domain object will have lot more attributes along with behavior while EModel class Person is just a container object and a DTO on service tier would populate it to business object and vice versa.

Presenter listens to View. It provides initial data required by the view. When view raises a request it is handled by presenter which talks to the service and provides the requested data.

Sample class diagrams

Service class diagram

Service

GetPersons return a collection of Person type objects.

Person (Emodel/DTO) class diagram

Person (Emodel or DTO)

Presenter and other class diagrams

Presenter and other class diagrams

 

ISearchPerson is the interface that View will implement. Presenter allows constructor injection for type ISearchPerson interface. Presenter also listens to Search event and associates a handler on instantiation.

Sample code

ISearchPerson Interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EModel;

public delegate void VoidHandler();

namespace Presenter
{
    /// <summary>
    /// Contract between View and Presenter is this interface.
    /// </summary>
    public interface ISearchPerson
    {
        string Name { get; }
        string Age { get;  }
        string Dept { get; }
        event VoidHandler Search;
        List<Person> Persons { set; }
    }
}

Person Presenter

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EModel;
using Service;
namespace Presenter
{
    /// <summary>
    /// Duty of presenter is to serve a contract. Presenter can serve multiple contracts. Each contract 
    /// is implemented by one or more views. 
    /// </summary>
    public class PersonPresenter
    {
        ISearchPerson _View;

        /// <summary>
        /// Constructor injection
        /// </summary>
        /// <param name="searchPerson"></param>
        public PersonPresenter(ISearchPerson searchPerson)
        {
            searchPerson.Search += new VoidHandler(_Search);
            _View = searchPerson;
        }

        /// <summary>
        /// Handler for search event
        /// </summary>
        private void _Search()
        {
            List<Person> persons = AdministrationService.GetPersons(_View.Name, _View.Age, _View.Dept);

            //Setting contract for view to get information from presenter
            _View.Persons = persons;
        }

    }
}

View (ASP.NET page) code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Presenter;
using EModel;

namespace WebMVP
{
    public partial class _Default : System.Web.UI.Page, ISearchPerson
    {
        #region Private variables

        private List<Person> persons = new List<Person>();
        private PersonPresenter presenter;

        #endregion

        /// <summary>
        /// page load
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            //This method can be used to instantiate presenter and inject view which is calling it.
            InitPresenter();
        }

        /// <summary>
        /// Check if presenter is there initialize
        /// </summary>
        private void InitPresenter()
        {
            if (presenter == null)
                presenter = new PersonPresenter(this);
        }

        #region ISearchPerson Members

        /// <summary>
        /// Name
        /// </summary>
        public string Name
        {
            get { return txtName.Text; }
        }

        /// <summary>
        /// Age
        /// </summary>
        public string Age
        {
            get { return txtAge.Text; }
        }

        /// <summary>
        /// Department
        /// </summary>
        public string Dept
        {
            get { return txtDept.Text; }
        }

        /// <summary>
        /// Event
        /// </summary>
        public event VoidHandler Search;

        /// <summary>
        /// Persons returned by Presenter
        /// </summary>
        public List<EModel.Person> Persons
        {
            set { persons = value; }
        }

        #endregion

        /// <summary>
        /// Search button click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void _Search_Click(object sender, EventArgs e)
        {
            //Initialize presenter
            InitPresenter();

            //Raise event 
            if (Search != null)
                Search();

            //Bind view controls with contract properties/variables set by contract
            GridView1.DataSource = persons;
            GridView1.DataBind();
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *