Partager via


Using the Model-View-Presenter (MVP) Design Pattern to enable Presentational Interoperability and Increased Testability

What is MVP?

Model View Presenter (MVP) is a software design pattern which essentially isolates the user interface from the business logic. MVP is derived from the Model View Controller (MVC) pattern, and originally conceived by the renowned Agile software architect, Martin Fowler.

The principal behind the MVP pattern is that an implementing application should be split into three core components; Model, View and Presenter:

§ The Model component encapsulates all Business Logic and Data in the application. This may be a database transaction or a call to a web service, etc.

§ The View component represents the application’s Presentation layer (User Interface); this may be a standard Win Forms client, an ASP.NET Web part or Mobile client. In the MVP pattern, the View should be simplistic and responsible for rendering and accepting user input only.

§ The Presenter component is responsible for orchestrating all the application’s use cases. For example a sample operation would involve; taking user input from the View, invoking operations on the Model and if needed, setting data in the View to indicate the operation’s result.

View

Presenter

Model

 - Presenter

 - IView - Model

 

 

The advantages of implementing the MVP pattern in a project with a presentation tier are:

§ Isolation of User Interface from Business tier

§ Easily interchangeable Views (user interfaces)

§ Ability to test all code in the solution (excluding visual presentation and interaction)

 

 

Example

Throughout this document, the explanation of the MVP design pattern, code extracts and testing strategies will all refer to an example Search application. This search application is extremely basic and used for purely illustrative purposes only.

It consists of a single Use Case scenario; Search, which executes as follows:

1. User enters a search criteria (e.g. “test”)

2. The User initiates a search in the UI (e.g. Button click)

3. The View component handles the search event, and invokes the ‘Search()’ function on the Presenter

4. The Presenter extracts the search criteria string from the View’s ‘SearchCriteria’ property

5. The Presenter invokes the Business operation ‘Business Search’ on the Model, passing the search criteria as an argument

6. The Model returns all matching results for the given search criteria

7. The Presenter sets these results in the View component’s ‘Results’ property 

 

 

 

Detailed Explanation

Model

The MVP pattern is chiefly concerned with isolating the user interface from the business logic and as such the Model component’s design is not specified in any detail.

Since the Presentation component is responsible for orchestrating business use cases, the Presentation component contains a reference to both the View and Model components.

However it is generally accepted as good practice to decouple the Model from the Presentation component. To do this, it is recommended to create an interface for the Model which defines all the business logic operations contained in the Model. Then use the ‘Factory method’ pattern (a creational pattern) to return a concrete implementation of the Model for use in the Presentation component. This allows the Model to be interchanged without any modification to the Presentation component.

 

View

The View component represents the Presentation layer and can be implemented in a variety of UI technologies (Windows Forms, Web-Page, Web-Part, Silverlight etc) on a range of platforms (Client, Web, Mobile).

The View’s responsibilities are limited to rendering itself, accepting user input and handling user events on controls (e.g. Button clicks).

The View does not perform any business logic, or directly interact with the Model. Instead it invokes methods on the Presenter. The Presenter in-turn calls business operations on the Model and then [in some cases] sets properties on the View to indicate the operation’s result.

Because the View is designed to be fully interchangeable, the View component implements an interface which defines the methods and properties all concrete Views must implement.

 

 

The above diagram illustrates the architecture of the View component. To better understand how the View and Presenter components interact, these components’ will be discussed in the context of the example Search Web-Part application.

In the preceding diagram, the View component’s interface ‘IView’ defines a property ‘SearchCriteria’ of type string with read permissions and a property ‘Results’ of type string collection with write permissions.

For the purposes of the example Search application, it is necessary for any View to have an input which accepts the user’s search criteria (e.g. TextBox), and an output which displays the matching results (e.g. ListBox).

    public interface IView

    {

        string SearchCriteria { get; }

        List<string> Results { set; }

    }

 

The Presenter component is never given a direct reference to the View component’s concrete implementation. Instead it references the View component through the View Interface ‘IView’. This allows the View implementation to be changed (e.g. from a Web Part control to a Windows Form client) without the Presenter being modified. Therefore when the View’s interface is developed, thought should be given as to how the Presenter will interact with the View’s data. For example, in the example Search application, the Presenter will need to retrieve (‘get’) the user’s search criteria string and once the search has been performed (by the Model) the resultant output must be displayed to the user (‘set’).

It is also important to develop View interfaces using only Framework types or Business types (as defined in the Model), not presentation technology-specific types. E.g. it would be possible to define the ‘Results’ property to be of type System.Web.UI.WebControls.BulletedList, however this is specific to an ASP.NET Views and would be inefficient to implement on a Windows Form client View implementation.

 

    public class ViewImpl : WebPart, IView

    {

        private Presenter presenter;

        Label lblSearch = new Label() { Text = "Enter bank to search for: " };

        Button btnSearch = new Button() { Text = "Search" };

        BulletedList resultsList = new BulletedList();

        TextBox tbSearchCriteria = new TextBox();

        public ViewImpl()

        {

            this.presenter = new Presenter(this);

        }

        protected override void CreateChildControls()

        {

            this.Controls.Clear();

            this.btnSearch.Click += new EventHandler(btnSearch_Click);

            this.Controls.Add(this.lblSearch);

            this.Controls.Add(this.tbSearchCriteria);

            this.Controls.Add(this.btnSearch);

            this.Controls.Add(this.resultsList);

            this.ChildControlsCreated = true;

        }

        void btnSearch_Click(object sender, EventArgs e)

        {

            this.presenter.Search();

        }

        #region IView Members

        public string SearchCriteria

        {

            get { return this.tbSearchCriteria.Text; }

        }

        public List<string> Results

        {

            set

            {

                this.resultsList.Items.Clear();

                List<string> results = new List<string>(value);

                foreach (string r in results)

                {

                    this.resultsList.Items.Add(r);

                }

            }

        }

        #endregion

    }

 

The above code extract contains all the code required to implement a very basic search ASP.NET Web Part that also implements the IView interface.

The following list dissects the structure and members of the example Search Web Part View implementation:

§ Firstly, the variables are declared, including a reference to the Presenter, also a number of UI controls.

§ ViewImpl’s constructor, instantiates the Presenter, passing a reference to itself to the Presenter’s constructor. The Presenter’s constructor signature defines a parameter of type IView, and since ViewImpl implements IView, the self-reference can safely be passed. This allows the Presenter to invoke methods/properties on the IView instance and visa-versa allowing the View to invoke methods/properties on the Presenter instance.

§ ViewImpl overrides the Web Part method ‘CreateChildControls()’ so as to perform custom rendering of itself and its children. The ‘Search’ Button control also registers a new Event Handler to handle button clicks.

§ The Button click event handler, does not perform any logic or call any Business Operations, instead it simply invokes a ‘Search()’ method on the Presenter (note that the method does not take any parameters).

§ Next, the IView implementations. The ‘SearchCriteria’ property returns the Text property of the TextBox input control which contains the user’s search criteria. The Results property populates the List control with the data passed into it.

 

Separation of Concerns

Although the MVP pattern prescribes the implementation of only a very basic View with little or no logic, it is often desirable or necessary to include validation, error handling, initialization, localization and general house-keeping code. These operations are specific to be presentation-technology being employed and as such are not something that can be handled by the Presenter; however they are also not strictly related to the rendering of the View.

Therefore in the development of this simple Search example MVP application, it was decided to experiment with a separation of View concerns, whereby the View would be split into a Logic tier and a Presentational tier.

 

 

It is important to note that implementing the View as two separate classes can lead to more complex code and requires greater design consideration, as it may be prudent to declare certain IView members as abstract in the Logic tier as they require no presentational logic and should therefore be implemented in the Presentational tier only. This is just one example of a design consideration that must be taken when employing this pattern.

Therefore, this pattern is only really suited to complex Views where implementation-specific logic is essential.

 

 

 

 

Logic Tier

In the context of the Search web part example, the logic tier of the View component is responsible for:

§ Implementation of the IView interface – The body of these properties includes validation, error handling logic etc.

§ Inheriting from WebPart

§ Initialisation – Instantiating the Presenter, setting up resources specific to the implemented technology (e.g. Retrieving parameters from the HTTP request object)

§ Validation – E.g. Ensuring the results assigned to the Results string collection are valid

§ Error handling – E.g. Handling / throwing exceptions generated during the aforementioned operations

It is worth noting that although the Logic tier inherits from System.Web.UI.WebControls.WebParts.WebPart, it is marked as an abstract class to prevent instantiation. This is because it is only responsible for implementation-specific logic, not rendering of itself and child controls.

Presentational Tier

The Presentation tier then inherits from the Logic tier, and is solely responsible for all User Interface and Interaction matters. In the context of this Search example, these responsibilities are:

§ UI Initialisation – E.g. Webpart title, Size, Positioning etc

§ Creation of all constituent controls (Buttons, TextBoxes, Labels, Lists etc)

§ Rendering of these controls – E.g. CreateChildControls()

§ Event Handlers registered to constituent Controls

§ Overridden IView member implementations – The overridden IView members call the Logic tier’s IView members to perform validation etc, and then render the results to the respective UI controls.

 

 

Presenter

As illustrated in the View implementation C# code extract, the View (like many applications) is the entry-point for the application, and is responsible for creating the Presenter which in-turn creates the Model. The only difference between this example MVP search web part and a non-MVP web part is that the MVP View contains no business logic, apart from one call to invoke the Search operation on the Presenter instance. Apart from this, it is solely dedicated to the rendering of its constituent UI Controls and the getting and setting of the data therein.

    public class Presenter

    {

        private readonly IView view;

        public Presenter(IView viewImpl)

        {

            this.view = viewImpl;

        }

        public void Search()

        {

            this.view.Results = Model.Search(this.view.SearchCriteria);

     }

    }

 

The Presenter in the example Search application (see above code extract) is very simple and contains a reference to a View (through the IView contract), a constructor which takes an IView reference as its single parameter and a single method ‘Search()’.

The ‘Search()’ method in this example, illustrates how the Presenter interacts with the View. As shown in the code extract, the method does not take any arguments (such as the user’s ‘search criteria’). Instead it directly obtains the search criteria string from the View implementation because the Presenter knows that any View implementation must contain the read-only property ‘SearchCriteria’ of type string. Similarly, instead of returning the results of the Search through a return type (the ‘Search()’ method signature defines a return type of void), it directly sets the results to the write-only property ‘Results’ of type string collection.

Notice also, that the ‘Search()’ method does not perform the Search operation itself, instead it invokes the Model to perform this task since its primary responsibility is to execute business operations.

This example Presenter clearly illustrates the Presenter’s relationship with both the View and the Model. It does not know what View it is retrieving and setting data to, it is only aware of the IView interface which acts as a contract between the two components. Similarly, the Presenter does not need to know which Model it is invoking on behalf of the View. If the Presenter’s interaction with the Model is also performed using an interface (e.g. IModel) then it becomes decoupled from both of its sibling components.

Unfortunately, the Presenter illustrated above in the example Search application does not perform much intermediary business logic such as validation, event handling, logging and other middle-tier actions. However, it should be clear that these middle-tier actions are ideally suited to the Presenter and can be implemented and tested relatively easily.

 

Testing MVP Applications

The Problem

In traditional applications, the user interface presentational tier is tightly coupled to the business logic tier. This is because Win and Web applications contain ‘code-behind’ which performs operations both during the User Interface’s lifecycle (e.g. Page Load) and when the user interacts with the UI (event handler attached to a control’s event e.g. button click).

This code-behind becomes (particularly in large or complex applications) responsible for both rendering and business logic invocation. Therefore the code-behind is tightly coupled to both the Presentation tier and the Business tier. Furthermore, it is frequently necessary for the code-behind to invoke business operations and then transform the results, handle exceptions and perform complex flow-control based on the result of an operation etc. The resultant design can become unstructured and difficult to maintain.

A further, often neglected effect of bloated code-behind is an un-testable presentation tier. Although the appearance and interaction of a User Interface can only really be tested by either manual or scripted automation tests, the overuse of code-behind means there is a great deal of logic that happens in the event handlers in the code-behind, before execution ever passes to the well unit-tested Business tier.

 

The MVP Solution

Implementing the MVP design pattern greatly increases the ability to test the presentation tier, since the View component consists almost entirely of visual logic (UI controls and basic event handlers), and all the logic surrounding the calls to the data tiers (Model) is orchestrated by the Presenter.

This means that if the tests can invoke Presenter functions and inspect the state of the View, the tests will cover almost all logic code. The only part of the application left un-tested is the visual state of the user interface controls. Determining this is extremely difficult to perform programmatically and as such is normally left to manual or scripted automation tests.

The best way of testing an MVP solution is to use a ‘Mock Object’ in place of the actual View. The mock View implements the IView interface, and is also customised to allow the tests access to View members which are inaccessible in the actual View.

This technique of mocking the View was used to test the example Search application, as the following code extract illustrates.

 

    public class ViewMock : IView

    {

        public SearchPresenter Presenter { get; set; }

        public ViewMock()

        {

            this.Presenter = new Presenter(this);

        }

        #region ISearchView Members

        public List<string> Results { get; set; }

        public string SearchCriteria { get; set; }

        #endregion

    }

 

The above code extract illustrates the Mock View implementation created to test the example Search application. It is worth noting however that although the mock View implements the IView interface as the real web part View does, it has an extra property ‘Presenter’ and both set and get permissions on all implemented IView properties (‘Results’ and ‘SearchCriteria’).

This extra ‘Presenter’ property allows the Unit Test to obtain the Presenter instance without resorting to reflection (it is necessary to obtain the View’s Presenter instance, since it is the Presenter’s functions which will be tested).

Furthermore, the get and set assessors on the IView implemented properties allow the Unit Test to inspect the mock View’s property values before and after a Presenter’s method has been invoked (so as to assert method pre and post conditions).

Without testing the mock View and instead testing the actual View, it would not be possible (without using complex and unreliable reflection) to obtain the Presenter to invoke business operations on, set-up test scenarios and inspect the View’s state during tests.

One concern when testing a mocked View is that the extra properties and increased access to the View members is that the View being tested no longer resembles the actual View in respect of design and structure. This concern is easily remedied since the unit tests holds references to both the mock View instance directly, and also via an IView reference (see code extract below).

        /// <summary>

        /// Search test. Results expected.

        /// </summary>

        [TestMethod()]

        public void SearchTest_ResultsExpected()

        {

            // Setup test pre-reqs and components

            ViewMock mockView = new ViewMock();

            Presenter presenter = mockView.Presenter;

            mockView.SearchCriteria = "test";

           

            ISearchView testView = mockView;

            Assert.IsTrue(testView.SearchCriteria.Equals(mockView.SearchCriteria));

            // Search

            presenter.Search();

            // Assert results were found

            // (min # results returned from search is 1 - error detail ietm)

            Assert.IsTrue(mockView.ResultsSet.Count > 0);

            // Assert results were valid, given the search criteria

            foreach (SearchResult r in mockView.ResultsSet)

            {

                Assert.IsNotNull(r);

                Assert.IsTrue(r.Name.IndexOf(testView.SearchCriteria) >= 0);

            }

        }

 

 

A Further Mocking Example

The mocking solution illustrated above meets the requirements of most simplistic Views that will be developed using the MVP pattern. However as discussed in the section ‘View – Separation of Concerns’ sometimes it is necessary to implement a small amount of presentation technology-specific logic to perform validation, conversion of user input to Business types and visa versa, error handling etc.

The mock View illustrated previously does not implement any of this logic and would require extra development time to re-implement the View logic inside the mock View. A workaround for this is to split the View into two tiers; a Logic tier and a Presentation tier and then create the mock View as a subclass of the abstract View Logic tier (see diagram below).

 

 

This allows the mock view access to the Presenter instance, the ability to expose new properties and modify assessors to allow easy inspection of View state during tests and most importantly the ability to invoke all presentation technology-specific logic held in the View Logic tier through invocation of the mock View’s base class.

Therefore, when a unit test is run which invokes a method on the Presenter, which sets a property in the mock View, the overridden mock View’s property can invoke its base class’ property which in turn executes any validation etc against the input (see code flow diagram below).

    [TestMethod()]

    public void SearchTest_ResultsExpected()

    {

        // Mock View setup (hidden)...

        presenter.Search();

        // Assertions (hidden)...

        }

    }

    public class Presenter

    {

 public void Search()

        {

            this.view.Results = Model.Search(this.view.SearchCriteria);

        }

        // Rest of Presenter class hidden...

    }

    public class MockViewLogic : ViewLogic

    {

        public override List<string> Results

        {

            set

            {

                base.Results = value;

            }

        }

        // Rest of mock view hidden...

    }

 

    public class ViewLogic: WebPart, IView

    {

 public virtual List<string> Results

        {

            set

            {

        // Validation

        // Error handling

        // Localisation

        // etc...

            }

        }

        // Rest of view logic abstract class hidden...

    }

This form of mocking the MVP View allows for more pervasive and complete testing of the View component.

Presentational Interoperability

The following code extracts are given so as to illustrate how to implement a View for the example Search application, given its simple IView interface. One View is implemented as an ASP.NET web part, the other as a client side WPF application.

ASP.NET Web Part Implementation

 

 

   

    public class ViewImpl : WebPart, IView

    {

        private Presenter presenter;

        Label lblSearch = new Label() { Text = "Enter bank to search for: " };

        Button btnSearch = new Button() { Text = "Search" };

        BulletedList resultsList = new BulletedList();

        TextBox tbSearchCriteria = new TextBox();

        public ViewImpl()

        {

            this.presenter = new Presenter(this);

        }

        protected override void CreateChildControls()

        {

            this.Controls.Clear();

            this.btnSearch.Click += new EventHandler(btnSearch_Click);

            this.Controls.Add(this.lblSearch);

            this.Controls.Add(this.tbSearchCriteria);

            this.Controls.Add(this.btnSearch);

            this.Controls.Add(this.resultsList);

            this.ChildControlsCreated = true;

        }

        void btnSearch_Click(object sender, EventArgs e)

        {

            this.presenter.Search();

        }

        #region IView Members

        public string SearchCriteria

        {

            get { return this.tbSearchCriteria.Text; }

        }

        public List<string> Results

        {

            set

            {

                this.resultsList.Items.Clear();

                List<string> results = new List<string>(value);

                foreach (string r in results)

                {

                    this.resultsList.Items.Add(r);

                }

            }

        }

        #endregion

    }

WPF Implementation

 

 

    public partial class Client : Window, IView

    {

        SearchPresenter presenter;

        public Client()

        {

            this.presenter = new SearchPresenter(this);

            InitializeComponent();

        }

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            this.presenter.Search();

        }

        #region ISearchView Members

        public List<string> Results

        {

            set

            {

                this.listBox1.Items.Clear();

                foreach (SearchResult r in value)

                {

                    this.listBox1.Items.Add(r.ToString());

                }

            }

        }

        public string SearchCriteria

        {

            get { return this.tbSearchCriteria.Text; }

        }

        #endregion

    }

 

§ https://blog.vuscode.com/malovicn/archive/2007/11/04/model-view-presenter-mvp-design-pattern-close-look-part-2-passive-view.aspx

§ https://weblogs.asp.net/bsimser/archive/2006/07/18/Model_2D00_View_2D00_Presenter-Pattern-with-SharePoint-Web-Parts.aspx

§ https://geekswithblogs.net/Podwysocki/archive/2007/12/20/117881.aspx

§ https://en.wikipedia.org/wiki/Model_View_Presenter

§ https://en.wikipedia.org/wiki/Mock_object

§ https://msdn.microsoft.com/en-us/magazine/cc188690.aspx

§ https://www.martinfowler.com/eaaDev/ModelViewPresenter.html 

§ https://en.wikipedia.org/wiki/Factory_method

Comments

  • Anonymous
    September 09, 2008
    PingBack from http://hoursfunnywallpaper.cn/?p=5292

  • Anonymous
    October 30, 2012
    Very clear example. One thing to note with the MVP pattern is that things can get very busy if you start implementing many views for an entity for example. IAddressDetailView, IAddressListView etc... and also if you try to use 2 views in one form and they have parameters with the same name. Still a great way to do things.