The Model View Presenter pattern in C# – a minimalist implementation

The Model View Presenter (MVP) is a design pattern that is particularly useful for implementing user interfaces in such a way as to decouple the software into separate concerns, such as those intended for data processing and storage (model), business logic, the routing of user commands, etc, thereby making more of your code available for unit testing.

The MVP design pattern separates the following concerns:

The Model. Stores the data to be displayed or acted upon in the user interface.

The View. A passive user interface that displays the model data and routes user-initiated events such as mouse click commands to the presenter to act upon that data.

The Presenter. Acts upon the model and the view. It retrieves data from the model, and displays it in the view.

MVP_3

Broadly speaking, there are two types of MVP – the Supervising Presenter and the Passive View. The Supervising Presenter allows coupling between the View and the Model, while the Passive View forbids it. Passive View is to be preferred if we wish to maximize unit testability, while encouraging thin Views that contain no logic. The Passive View is the approach described in this example.

When the user submits a request such as a mouse click on a button control, the View (which will have created its Presenter and Model objects) accepts the request and delegates the request to the Presenter object, which will invoke a chosen method of its own.

The Presenter, which is able to obtain both the state of the current View (textbox text, listbox selection integer etc) as well as the current Model data, will perform any calculations or business logic required of it and update the View with the results, and operate on the Model as appropriate. The View instantiates the Presenter object in its constructor, thereby providing a reference to itself.

Let’s examine the MVP operation using a simple WinForm as a sequence of steps:

1. The User submits a request such as pressing the ‘Set’ button control:

MVP_1

2. The View creates the Presenter object and is injected with the Model via its constructor. The View accesses the Presenter directly. It accepts the request and delegates the User input to the Presenter. The View can also respond to any Model-initiated events by subscribing to them.

namespace ModelViewPresenter
{
    public partial class Form1 : Form, IView
    {
        private Presenter presenter = null;
        private readonly Model m_Model;

        public Form1(Model model)
        {
            m_Model = model;
            InitializeComponent();
            presenter = new Presenter(this, m_Model);
            SubscribeToModelEvents();
        }

        public string TextValue
        {
            get
            {
                return textBox1.Text;
            }
            set
            {
                textBox1.Text = value;
            }

        }

        private void Set_Click(object sender, EventArgs e)
        {
            presenter.SetTextValue();
        }

        private void Reverse_Click(object sender, EventArgs e)
        {
            presenter.ReverseTextValue();
        }

        private void SubscribeToModelEvents()
        {
            m_Model.TextSet += m_Model_TextSet;         
        }

        void m_Model_TextSet(object sender, CustomArgs e)
        {
            this.textBox1.Text = e.m_after;
            this.label1.Text = "Text changed from " + e.m_before + " to " + e.m_after;
        }
    }
}

View interface as follows:

namespace ModelViewPresenter
{
    public interface IView
    {
        string TextValue { get; set; }
    }
}

3. The Presenter acts as a kind of ‘middle man’ between the Model and View interfaces. It obtains the current state from the View Interface (textbox text, listview item selected etc) and invokes a chosen method of its own, such as performing some calculation or business logic. The Presenter also commands the Model state changes as appropriate:

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

namespace ModelViewPresenter
{
    public class Presenter
    {
        private readonly IView m_View;
        private IModel m_Model;

        public Presenter(IView view, IModel model)
        {
            this.m_View = view;
            this.m_Model = model;
        }

        public void ReverseTextValue()
        {
            string reversed = ReverseString(m_View.TextValue);
            m_Model.Reverse(reversed);
        }

        public void SetTextValue()
        {
            m_Model.Set(m_View.TextValue);
        }

        private static string ReverseString(string s)
        {
            char[] arr = s.ToCharArray();
            Array.Reverse(arr);
            return new string(arr);
        }
    }
}

4. The Model updates and stores the changes made to its state. As a result of changes made to its state, the Model can also raise events of its own, to notify its clients of the changes so that they can act accordingly, such as modifying the User Interface display.

namespace ModelViewPresenter
{
    public class Model : IModel
    {
        private string m_textValue;

        public event EventHandler<CustomArgs> TextSet;
        public event EventHandler<CustomArgs> TextReverse;

        public Model()
        {
            m_textValue = "";
        }

        public void Set(string value)
        {
            string before = m_textValue;
            m_textValue = value;
            RaiseTextSetEvent(before, m_textValue);
        }

        public void Reverse(string value)
        {
            string before = m_textValue;
            m_textValue = value;
            RaiseTextSetEvent(before, m_textValue);
        }

        public void RaiseTextSetEvent(string before, string after)
        {
            TextSet(this, new CustomArgs(before, after));
        }
    }

    public class CustomArgs : EventArgs
    {
        public string m_before { get; set; }
        public string m_after { get; set; }

        public CustomArgs(string before, string after)
        {
            m_before = before;
            m_after = after;
        }
    }
}

Model abstraction as follows:

namespace ModelViewPresenter
{
    public interface IModel
    {
        void Set(string value);
        void Reverse(string value);
    }
}

As a result of changes made to its state, the Model can also raise events of its own, to notify its clients of the changes so that they can act accordingly, such as modifying the User Interface display:

MVP_2

An important feature of the MVP is that the Model can enable multiple Views to observe its data. Another important distinction is that the Model is neither aware of the View nor the Presenter.

Download the Visual Studio 2010 project:

http://www.technical-recipes.com/Downloads/ModelViewPresenter.zip

Leave a Reply