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:

2. 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.

using MVP.Presenter;
using MVP.Views;
using System.Runtime.CompilerServices;

namespace MVP
{
    public partial class Form1 : Form, IView
    {
        public event EventHandler<string>? SetText;
        public event EventHandler<string>? ReverseText;
        public event EventHandler? ClearText;

        readonly TextPresenter? presenter = null;

        public Form1()
        {
            InitializeComponent();
            presenter = new(this);
        }

        private void ButtonSet_Click(object sender, EventArgs e)
        {
            presenter?.SetTextDisplay(InputText);
            SetText?.Invoke(this, InputText);
        }

        private void ButtonReverse_Click(object sender, EventArgs e)
        {
            var currentText = LabelText;
            presenter?.ReverseTextDisplay();
            var newText = InputText;
            ReverseText?.Invoke(this, currentText);
        }

        private void buttonClear_Click(object sender, EventArgs e)
        {
            presenter?.ClearTextDisplay();
            ClearText?.Invoke(this, EventArgs.Empty);
        }

        public string LabelText
        {
            get { return labelTxt.Text; }
            set { labelTxt.Text = value; }
        }

        public string InputText
        {
            get { return inputTxt.Text; }
            set { inputTxt.Text = value; }
        }
    }
}

View interface as follows. This contains properties to set and retrieve the content of view controls such as text boxes. Also it may contain events to notify of user interactions such as button clicks, mouse events etc.

namespace MVP.Views
{
    public interface IView
    {
        string LabelText { get; set; }
        string InputText { get; set; }
        event EventHandler<string>? SetText;
        event EventHandler<string>? ReverseText;
        event EventHandler? ClearText;
    }
}

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 MVP.Models;
using MVP.Views;
using System.Windows.Forms;

namespace MVP.Presenter
{
    public  class TextPresenter
    {
        private readonly IView _textDisplay;
        public TextPresenter(IView textDisplay)
        {
            _textDisplay = textDisplay;
            textDisplay.SetText += (o, e) => {
                MessageBox.Show("Text label set to: " + e.ToString());
            };

            textDisplay.ReverseText += (o, e) => {
                MessageBox.Show("Text label reversed from " + e.ToString() + " to: " + _textDisplay.LabelText);
            };

            textDisplay.ClearText += (o, e) => {
                MessageBox.Show("Text label cleared");
            };
        }

        public void ReverseTextDisplay()
        {
            TextDisplay textDisplay = new();
            string currentText = _textDisplay.LabelText;
            var reverseText = textDisplay.Reverse(currentText);
            _textDisplay.LabelText = reverseText;
        }

        public void SetTextDisplay(string text)
        {
            _textDisplay.LabelText = text;
        }

        internal void ClearTextDisplay()
        {
            _textDisplay.LabelText = "";
            _textDisplay.InputText = "";
        }
    }
}

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 MVP.Models
{
    public class TextDisplay : ITextDisplay
    {
        public string Reverse(string text)
        {
            char[] charArray = text.ToCharArray();
            Array.Reverse(charArray);
            return new string(charArray); 
        }
    }
}

Model abstraction as follows:

namespace MVP.Models
{
    public interface ITextDisplay
    {
        string Reverse(string text);
    }
}

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:

And on clicking the REVERSE button see that the text label is updated as shown:

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 2022 project:
https://www.technical-recipes.com/Downloads/MVP.zip

`