September 2010

Volume 25 Number 09

Cutting Edge - Better Web Forms with the MVP Pattern

By Dino Esposito | September 2010

Dino EspositoThe advent of the Model-View-Controller (MVC) pattern is an important milestone in software development. It showed that designing applications with separation of concerns in mind improved both the development process and the finished application. It also offered a reproducible approach for putting that pattern into practice.

MVC is not perfect, though, so several variations of it appeared over the years.

Because it was devised in the 1980s, one problem that’s surfaced is that MVC does not directly accommodate development for the Web. Adapting MVC to the Web took a few more years and led to the development of more specific MVC patterns such as Model2. (Model2 is the actual flavor of MVC implemented by Castle MonoRail and ASP.NET MVC.)

In a more general context, the Model-View-Presenter (MVP) pattern is an evolution of MVC that separates view and model neatly by placing the controller in between as a mediator. Figure 1 illustrates the behavior of an application designed with the MVP pattern.

Figure 1 Using the MVP Pattern

Figure 1 Using the MVP Pattern

In this article, I’ll first present a possible (and relatively standard) implementation of the MVP pattern for ASP.NET Web Forms and then discuss the application of the pattern, its benefits to the team, and compare it to ASP.NET MVC and Model-View-ViewModel (MVVM) as it has been implemented in Windows Presentation Foundation (WPF) and Silverlight.

MVP at a Glance

MVP is a derivative of the original MVC pattern developed at Taligent (now part of IBM) in the 1990s. The paper available for download at wildcrest.com/Potel/Portfolio/mvp.pdfoffers a nice introduction to MVP and the ideas behind it.

The creators of MVP neatly separated the model (the data being worked on in the view) from the view/controller pair. They also renamed the controller as presenter to reinforce the idea that in the pattern, the role of the controller is that of a mediator between the user and the application. The presenter is the component that “presents” the UI to the user and accepts commands from the user. The presenter contains most of the presentation logic and knows how to deal with the view and the rest of the system, including back-end services and data layers.

A key innovation in MVP is the fact that the details of the view are abstracted to an interface (or base class). The presenter talks to an abstraction of the view, which makes the presenter itself a highly reusable and highly testable class. This enables two interesting scenarios.

First, the presentation logic is independent from the UI technology being used. Subsequently, the same controller could be reused in Windows and Web presentation layers. In the end, the presenter is coded against an interface and it can talk to any object that exposes that interface—whether a Windows Forms object, an ASP.NET Page object, or a WPF Window object.

Second, the same presenter could work with different views of the same application. This is an important achievement with regard to Software as a Service (SaaS) scenarios where an application is hosted on a Web server and offered as a service to customers, each requiring its own customized UI.

It goes without saying that both benefits are not necessarily applicable in all situations. Whether these benefit you mostly depends on the application and navigation logic you expect to employ in your Windows and Web front end. However, when the logic is the same, you can reuse it via the MVP model.

MVP in Action

When implementing the MVP pattern, the first step is defining the abstraction for each required view. Each page in an ASP.NET application and each form in a Windows (or WPF/Silverlight) application will have its own interface to talk to the rest of the presentation layer. The interface identifies the data model that the view supports. Each logically equivalent view will have the same interface regardless of the platform.

The view abstraction incorporates the model the view recognizes and works with and can extend it with some ad hoc methods and events useful to favor a smooth interaction between the presenter and the view. Figure 2 shows a possible abstraction for the view rendered in Figure 3 that’s being used by a simple to-do list application.

Figure 2 An Example of a View Abstraction

public interface IMemoFormView {
  String Title { get; set; }
  String Summary { get; set; }
  String Location { get; set; }
  String Tags { get; set; }
  DateTime BeginWithin { get; set; }
  DateTime DueBy { get; set; }
  String Message { get; set; }
  Int32 GetSelectedPriorityValue();
  void FillPriorityList(Int32 selectedIndex);
  Boolean Confirm(String message, String title);
  void SetErrorMessage(String controlName);
}

In Figure 3 you also see how members in the interface match visual elements in the form.

Figure 3 Binding Members of the Interface to Visual Elements

Figure 3 Binding Members of the Interface to Visual Elements

The fundamental point is that any interaction between the presenter and the UI must happen through the contract of the view. Any button clicking, any selection, any typing must be forwarded to the presenter and handled there. If the presenter needs to query for some data in the view, or to pass data down to the view, there should be a method in the interface to account for that.

Implementing the View Contract

The interface that represents the view must be implemented by the class that represents the view itself. As mentioned, the view class is the page in ASP.NET, the form in Windows Forms, the Window in WPF and the user control in Silverlight. Figure 4 shows an example for Windows Forms.

Figure 4 A Possible Implementation of the View Class

public partial class MemoForm : Form, IMemoFormView {
  public string Title {
    get { return memoForm_Text.Text; }
    set { memoForm_Text.Text = value; }
    ...
  }
  public DateTime DueBy {
    get { return memoForm_DueBy.Value; }
    set { memoForm_DueBy.Value = value; }
  }
  public int GetSelectedPriorityValue() {
    var priority = 
      memoForm_Priority.SelectedItem as PriorityItem;
    if (priority == null)
      return PriorityItem.Default;
    return priority.Value;
  }
  public void FillPriorityList(int selectedIndex) {
    memoForm_Priority.DataSource = 
      PriorityItem.GetStandardList();
    memoForm_Priority.ValueMember = "Value";
    memoForm_Priority.DisplayMember = "Text";
    memoForm_Priority.SelectedIndex = selectedIndex;
  }
  public void SetErrorMessage(string controlName) {
    var control = this.GetControlFromId(controlName);
    if (control == null)
      throw new NullReferenceException(
        "Unexpected null reference for a form control."); 
    memoForm_ErrorManager.SetError(control, 
      ErrorMessages.RequiredField);
  }
  ...
}

As you can see, properties are implemented as wrappers for some properties on visual controls. For example, the Title property is a wrapper for the Text property of a TextBox control. Similarly, the DueBy property wraps the Value property of a DatePicker control. More importantly, the interface shields the presenter class from the details of the UI for a given platform. The same presenter class created to interact with the IMemoFormView interface can deal with any object that implements the interface, blissfully ignoring the details of the programming interface of underlying controls.

How would you deal with UI elements that require a collection of data, such as a drop-down list? Should you use data binding (as in Figure 4) or should you opt for a simpler approach that keeps the view passive and devoid of any presentation logic?

That’s up to you. In response to these kinds of questions, the MVP pattern has been split in two separate patterns—Supervising Controller and Passive View—whose primary difference is just the amount of code in the view. Using data binding for populating the UI (see Figure 4) adds some presentation logic to the view and would make it gain the flavor of a supervising controller.

The more logic you have in the view, the more you should care about testing. And testing a piece of UI is a task that can’t be easily automated. Going for a supervising controller or opting for a thinner and dumber view is merely a judgment call.

The Presenter Class

The controls in the view capture any user gesture and trigger an event to the view, such as a button-click or a selected-index change. The view contains simple event handlers that dispatch the call to the presenter that’s in charge the view. When the view is loaded for the first time, it creates an instance of its presenter class and saves that internally as a private member. Figure 5 shows the typical constructor of a Windows Form.

Figure 5 Creating an MVP Form

public partial class Form1 : 
  Form, ICustomerDetailsView {
  private MemoFormPresenter presenter;
  public Form1() {
    // Framework initialization stuff
    InitializeComponent();
    // Instantiate the presenter
    presenter = new MemoFormPresenter(this);
    // Attach event handlers
    ...
  }
  private void Form1_Load(
    object sender, EventArgs e) {
    presenter.Initialize();
  }
  ...
}

The presenter class typically receives a reference to the view through its constructor. The view holds a reference to the presenter and the presenter holds a reference to the view. However, the presenter knows the view only through the contract. The presenter works by segregating any view object it receives to its contracted view interface. Figure 6 shows the basics of a presenter class.

Figure 6 A Sample Presenter Class

public class MemoFormPresenter {
  private readonly IMemoFormView view;
  public MemoFormPresenter(IMemoFormView theView) {
    view = theView;
    context = AppContext.Navigator.Argument 
      as MemoFormContext;
    if (_context == null)
      return;
  }
 
  public void Initialize() {
    InitializeInternal();
  }
  private void InitializeInternal() {
    int priorityIndex = _context.Memo.Priority;
    if (priorityIndex >= 1 && priorityIndex <= 5)
      priorityIndex--;
    else
      priorityIndex = 2;
    if (_context.Memo.BeginDate.HasValue)
      _view.BeginWithin = _context.Memo.BeginDate.Value;
    if (_context.Memo.EndDate.HasValue)
      _view.DueBy = _context.Memo.EndDate.Value;
      _view.FillPriorityList(priorityIndex);
      _view.Title = _context.Memo.Title;
      _view.Summary = _context.Memo.Summary;
      _view.Tags = _context.Memo.Tags;
      _view.MemoLocation = _context.Memo.Location;
  }
  ...
}

The constructor receives and caches the reference to the view and initializes the view using the public interface represented by the contract. The context object you see used in the code of Figure 6 is any input data the presenter needs to receive from the caller in order to initialize the view. This information is not necessary in all cases, but it turns out to be necessary when you use the form to edit some data or when you have dialog boxes to display some information.

Initializing the view is as simple as assigning values to the members of a class, except that now any assignment results in an update to the UI.

The presenter class also contains a number of methods that execute in response to any requests from the UI. Any clicking or user action is bound to a method on the presenter class:

private void memoForm_OK_Click(
  object sender, EventArgs e) {
  presenter.Ok();
}

The presenter method uses the view reference to access input values and updates the UI in the same way.

The presenter is also responsible for navigation within the application. In particular, the presenter is responsible for enabling (or disabling) sub-views and command navigation to the next view.

A sub-view is essentially a subset of the view. It’s typically a panel that can be expanded or collapsed according to the context or perhaps a child window—either modal or modeless. The presenter controls the visibility of sub-views through members (mostly Boolean members) on the view interface.

What about transferring control to another view (and presenter)? You create a static class that represents the application controller—that is, the central console that holds all the logic to determine the next view. Figure 7 shows the diagram of the application controller.

Figure 7 The Application Controller

Figure 7 The Application Controller

The application controller class represents the shell that presenters invoke to navigate elsewhere. This class will have a NavigateTo method that implements the workflow that determines the next view or that simply moves to the specified view. The workflow can be anything—as complex as a real workflow or simply a sequence of IF statements. The logic of the workflow can be statically coded in the application controller or imported from an external and pluggable component (see Figure 8).

Figure 8 Implementation of an Application Controller

public static class ApplicationController {
  private static INavigationWorkflow instance;
  private static object navigationArgument;
  public static void Register(
    INavigationWorkflow service) {
    if (service == null)
      throw new ArgumentNullException();
    instance = service;
  }
  public static void NavigateTo(string view) {
    if (instance == null)
      throw new InvalidOperationException();
    instance.NavigateTo(view);      
  }
 
  public static void NavigateTo(
    string view, object argument) { 
    if (instance == null)
      throw new InvalidOperationException();
    navigationArgument = argument;
    NavigateTo(view);
 }
 public static object Argument {
   get { return navigationArgument; }
 }
}

The actual navigation logic in the workflow component will use a platform-specific solution to switch to a different view. For Windows Forms it will use methods to open and display forms; in ASP.NET it will use the Redirect method on the Response object.

MVP and ASP.NET MVC

ASP.NET MVC is based on a flavor of the MVC pattern that has a few things in common with MVP. The controller in MVC is a mediator between the view and the back end. The controller doesn’t hold a reference to the view, but fills up a model object and passes that to the view using the services of an intermediate component—the view engine.

In a way, the view is abstracted through the model, whose struc­ture reflects the characteristics of the view and its UI. Navigation is managed by the controller, which selects the next view from the context of each action. It does that using some built-in logic. Should the logic be particularly complex for a given controller method—frankly, not something that will happen every day—you can always introduce a workflow component that determines the next view to select.

What about Web Forms? Web Forms lends itself well to host an MVP implementation. However, it should be clear that all you get is adding layers in the context of the postback event. Anything before the postback cannot be incorporated, nor can anything that happens after the postback event. A full MVP implementation that expands to cover the full lifecycle is not possible in Web Forms, but even adding MVP around the postback is a good thing and will significantly increase the level of testability
of Web Forms pages.

MVP and MVVM

What about, instead, MVP and MVVM in the context of WPF and Silverlight applications? MVVM is a variation of MVP also known as Presentation Model. The idea is that the view model is incorporated in the presenter class and the presenter class exposes public members that the view will read and write. This happens through two-way data binding. At the end of the day, you can call MVVM as a special flavor of MVP particularly suited to rich UIs and to frameworks (like WPF) that promote this ability.

In MVVM, the view is data-bound to properties on the presenter class (the view model). Anything the user does updates these properties in the presenter. Any requests from the user (commands in WPF) are handled via a method on the presenter class. Any results the presenter method calculates are stored in the view model and made available via data binding to the view. In WPF and Silverlight, there’s nothing that prevents you from using a manual implementation of the MVP pattern. However, it turns out that tools such as Blend will make it simpler yet effective to use MVVM via data binding.

Postback

MVP provides guidance on how to manage heaps of views and, quite obviously, comes at a cost: the cost of increased complexity in the application code. As you can imagine, these costs are easier to absorb in large applications than in simple programs. MVP, therefore, is not just for any application. Based on a contract that represents the view, MVP allows for designers and developers to work in parallel, which is always a good thing in any development scenario. MVP keeps the presenter class as a standalone and isolated from the view. In Web Forms, MVP represents the only reasonable way to add testability at least to the code that executes the postback.


Dino Esposito is the author of “Programming ASP.NET MVC” from Microsoft Press and the coauthor of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Based in Italy, Esposito is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos.

Thanks to the following technical experts for reviewing this article: Don Smith and Josh Smith