Showing posts with label MVP. Show all posts
Showing posts with label MVP. Show all posts

Sunday, August 2, 2009

MVVM for .NET Winforms – MVP-VM (Model View Presenter - View Model) Introduction

This post introduces the MVP-VM (Model View Presenter – Model View) design pattern, which is the windows forms (winforms) equivalent of WPF/Silverlight MVVM. The MVP-VM pattern is best suited to winforms applications that require full testing coverage and use data binding extensively for syncing the presentation with the domain model.

image

Evolution

Before we start digging deep into MVP-VM, lets have a quick review of the patterns from which it has evolved.

image

Presentation Model

Martin Fowler introduced the Presentation Model pattern as a way of separating presentation behavior from the user interface, mainly to promote unit testing. With Presentation Model every View has Presentation Model that encapsulates its presentation behavior (such as how to handle buttonXXX click) and state (whether a check box is checked/unchecked).

image

Whenever the View changes it informs its Presentation Model about the change, in response the Presentation Model changes the Model as appropriate, reads new data from the Model and populates its internal view state. In turn, the View updates the screen according to the Presentation Model updated view state. 

The downside of this pattern it that a lot of tedious code is required in order to keep the Presentation Model and the View synchronized. A way to avoid writing the synchronization code is to bind the Presentation Model properties to the appropriate widgets on the View such that changes made to the Model will automatically reflect on the View, and changes made by the user will automatically flow from the View, through the Presentation Model to the underlying Model object.

MVVM  (Model View View Model) for WPF

MVVM (Model View View Model) introduces an approach for separating the presentation from the data in environments that empower data binding such as WPF and Silverlight (see Developing Silverlight 4.0 Three Tiers App with MVVM). As you can see in the picture bellow, MVVM is almost identical to the Presentation Model pattern, just instead of 'Presentation Model' – we have 'View Model' and the two way data binding happens automatically with the help of WPF/Silverlight runtime (read more). 

image

With WPF, the bindings between View and View Model are simple to construct because each View Model object is set as the DataContext of its pair View. If property value in the View Model changes, the change automatically propagate to the View via data binding. When the user clicks a button in the View, a command on the View Model executes to perform the requested action. The View Model, never the View, performs all modifications made to the Model data.

MVP-VM (Model View Presenter - View Model)

Starting from .NET framework 2.0, Visual Studio designer supports binding objects to user controls at design time which greatly simplifies and motivates the use of data binding in winforms applications. Even when designing simple UI without the use of any fancy pattern – it often makes sense to create View Model class that represent the View display (property for every widget) and bind it to the View at design time. You can read all about it in ‘Data Binding of Business Objects in Visual Studio .NET 2005/8’.

When creating .NET winforms application that consist of many Views that present complex domain model and includes complex presentation logic - it’s often makes sense to separate the Views from the domain model using the Model View Presenter pattern. One can use Supervising Controller or Passive View depending on the required testing coverage and the need for data binding.

With Supervising Controller data binding is simple but presentation logic cannot be fully tested since the Views (that are usually being mocked) are in charge of retrieving data from the Model. With Passive View the thin Views allow full testing coverage and the fact that the Presenter is in charge of the entire workflow greatly simplify testing. However, direct binding between the Model and the View is discouraged. For more details please refer to ‘Model View Presenter Design Pattern with .NET Winforms’.

MVP-VM is about combining the two patterns so we wont have to give up on Data Binding nor cut down on testability. This is achieved by adapting the Passive View pattern while allowing an indirect link between the Model and the View.

MVP-VM Overview

image

The View is in charge of presenting the data and processing user inputs. It is tightly coupled to the Presenter so when user input is triggered (a button has been clicked) it can directly call the appropriate method on the Presenter. It’s widgets are bound to the matching View Model properties such that when a property of the View Model changes – the linked widget is being changed as a result, and when the widget value changes – the View Model property is being changed as a result.

The View Model exposes properties that are bound to its matching View widgets. Some of its properties are linked directly to the Model object such that any change made to the Model object automatically translate to change on the View Model and as a result appear on the View and vise versa, and some of its properties reflect View state that is not related to Model data, e.g whether buttonXXX is enabled. In some cases the View Model is merely a snapshot of the Model object state so it exposes read-only properties. In this case the attached widgets cannot be updated by the user.

The Presenter is in charge of presentation logic. It creates the View Model object and assign it with the appropriate Model object/s and bind it to the View. When its being informed that a user input has been triggered it executes according to application rules e.g. command the Model to change as appropriate, make the appropriate changes on the View Model etc. It is synchronized with the Model via Observer-Synchronization so it can react to changes in the Model according to application rules. In cases were it’s more appropriate for the Presenter to change the View directly rather than though its View Model, the presenter can interact with the View though its interface.

The Model is a bunch of business objects that can include data and behaviors such as querying and updating the DB and interacting with external services. Such objects that only contain data are referred to as ‘data entities’.

How does it Work?

image

As you can see in the figure above, each UI widget is bound to a matching property on the ‘customer view model’, and each property of the ‘customer view model’ is linked to a matching property on the ‘customer data entity’. So for example, when the user changes the value of the ‘Name’ textbox – the ‘Name’ property of the ‘customer view model’ is automatically updated via data binding, which causes the update on the ‘Name’ property of the ‘customer data entity’. In the other direction, when the ‘customer data entity’ changes – the changes reflect on the ‘customer data model’ which causes the appropriate widgets on the view to change via data binding.

When the user clicks on the ‘Save’ button, the view responds and calls the appropriate method on the presenter, which responds according to application logic, in this case - it calls the ‘Save’ method of the ‘customer dao’ object.

In cases where the ‘application logic’ is more sophisticated, the presenter may bypass the ‘view model’ and make direct changes on the view through its abstraction. In some cases ‘view model’ property can be linked to view widget at one side – but not linked to model object at the other side, in such cases the ‘view model’ will be prompt to change by the presenter, which will result in the appropriate change on the view widget.

Case Study – MVP-VM

In the following case study the MVP-VM pattern is used to separate the concerns of a simple application that present list of customers and allows adding a new customer. We’ll focus on the ‘Add Customer’ screen.

image

Class Diagram – Add New Customer

image

The AddCustomerPresenter holds references to AddCustomerViewModel, AddCustomerView and CusomerDao (model). It references the AddCustomerViewModel and the AddCustomerView so it can establish data binding between the two, and it references the CussomerDao so it can change it and register to its events.

The AddCustomerView holds reference to the AddCustomerPresenter so it can call the ‘SaveClicked’ method when the ‘Save’ button is clicked.

Sequence Diagram - Initialization

image

The AddCustomerView is instantiated by some class in the application and injected with instance of the CusomerDao (model), it instantiate the AddCustomerPresenter and injects it with the CusomerDao and with itself. The AddCustomerPresenter prompts the CusomerDao to create a new CustomerDataEntity, instantiate the AddCustomerViewModel injecting it with the newly created CustomerDataEntity, and calls ‘ShowCustomer’ on the AddCustomerView in order to data bind it to the AddCustomerViewModel.

Sequence Diagram - Saving New Customer

image

The AddCustomerView responds to click on the ‘Save’ button and calls the appropriate method on the AddCustomerPresenter. The AddCustomerPresenter calls ‘ReadUserInput’ on the AddCustomerView which in response alerts its internal ‘binding source’ to reset binding, which causes the content of its widgets to reread into the AddCustomerViewModel (read more about data binding of business objects). The AddCustomerPresenter than evaluates the CustomerDataEntity (which was updated automatically since it’s linked to AddCustomerViewModel) and checks whether the new customer already exist in the data storage. In case there are no duplications it commands the CusomerDao (model) to save the customer.

Here’s the code:

View

   1: public partial class AddCustomerView : Form, IAddCustomerView
   2:  {
   3:      private AddCustomerPresenter m_presenter;
   4:  
   5:      public AddCustomerView(ICustomerDao dao)
   6:      {
   7:          InitializeComponent();
   8:  
   9:          m_presenter = new AddCustomerPresenter(this, dao);
  10:      }
  11:  
  12:      public void ShowCustomer(CustomerViewModel customerViewModel)
  13:      {
  14:          cusomerViewModelBindingSource.DataSource = customerViewModel;
  15:      }
  16:  
  17:      public void ReadUserInput()
  18:      {
  19:          cusomerViewModelBindingSource.EndEdit();
  20:      }
  21:  
  22:      public void ShowError(string message)
  23:      {
  24:          MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Information);
  25:      }
  26:  
  27:      private void m_btnSave_Click(object sender, EventArgs e)
  28:      {
  29:          m_presenter.SaveClicked();
  30:      }
  31:  
  32:      private void m_btnCancel_Click(object sender, EventArgs e)
  33:      {
  34:          m_presenter.CancellClicked();
  35:      }
  36:  }

Presenter

   1: public class AddCustomerPresenter 
   2: {
   3:     private IAddCustomerView m_view;
   4:     private ICustomerDao m_customerDao;
   5:     private CustomerViewModel m_viewModel;
   6:  
   7:     public AddCustomerPresenter(IAddCustomerView view, ICustomerDao customerDao)
   8:     {
   9:         m_view = view;
  10:         m_customerDao = customerDao;
  11:  
  12:         // Create the data entitry
  13:         CustomerDataEntity customerDataEntity = customerDao.CreateCustomerDataEntity();
  14:         CustomerViewModel customerViewModel = new CustomerViewModel(customerDataEntity);
  15:  
  16:         m_viewModel = customerViewModel;
  17:         
  18:         // Bind the ViewModel to the VIew
  19:         m_view.ShowCustomer(customerViewModel);
  20:     }
  21:  
  22:     public void SaveClicked()
  23:     {
  24:         m_view.ReadUserInput();
  25:  
  26:         CustomerDataEntity customerDataEntity = m_viewModel.CustomerDataEntity;
  27:         bool duplicateExist = !IsDuplicateOfExisting(customerDataEntity);
  28:         if (duplicateExist)
  29:         {
  30:             m_customerDao.Save(customerDataEntity);
  31:  
  32:             m_view.Close();
  33:         }
  34:         else
  35:         {
  36:             m_view.ShowError(string.Format("Customer '{0}' already exist", m_viewModel.Name));
  37:         }
  38:     }
  39:  
  40:     private bool IsDuplicateOfExisting(CustomerDataEntity newCustomerDataEntity)
  41:     {
  42:         CustomerDataEntity duplicateCustomerDataEntity = 
  43:             m_customerDao.GetByName(newCustomerDataEntity.Name);
  44:         
  45:         return duplicateCustomerDataEntity != null;
  46:     }
  47:  
  48:     public void CancellClicked()
  49:     {
  50:         m_view.Close();
  51:     }
  52: }

ViewModel

   1: public class CustomerViewModel
   2: {
   3:     private readonly CustomerDataEntity m_customerDataEntity;
   4:  
   5:     public CustomerViewModel(CustomerDataEntity customerDataEntity)
   6:     {
   7:         m_customerDataEntity = customerDataEntity;
   8:     }
   9:  
  10:     public string Name
  11:     {
  12:         get { return m_customerDataEntity.Name; }
  13:         set { m_customerDataEntity.Name = value; }
  14:     }
  15:  
  16:     public string CompanyName
  17:     {
  18:         get { return m_customerDataEntity.CompanyName; }
  19:         set { m_customerDataEntity.CompanyName = value; }
  20:     }
  21:  
  22:     public DateTime DateOfBirth
  23:     {
  24:         get { return m_customerDataEntity.DateOfBirth; }
  25:         set { m_customerDataEntity.DateOfBirth = value; }
  26:     }
  27:  
  28:     public int Age
  29:     {
  30:         get
  31:         {
  32:             int age = DateTime.Now.Year - m_customerDataEntity.DateOfBirth.Year;
  33:  
  34:             return age;
  35:         }
  36:     }
  37:  
  38:     public CustomerDataEntity CustomerDataEntity
  39:     {
  40:         get { return m_customerDataEntity; }
  41:     }
  42: }

Download

The case study can be downloaded from here or here

Wednesday, October 29, 2008

Model View Presenter (MVP) Design Pattern with .NET - Winforms vs. ASP.NET Webforms

MVP implementation for .NET desktop/smart-client (winforms) applications can take the form of Supervising Controller or Passive View. The main difference between the patterns is that 'Supervising Controller' encourages coupling between the View and the Model (via Observer-Synchronization) while 'Passive View' forbids it.

Typically, we'll chose 'Supervising Controller' if we have state full Model and we need the Views to immediately synchronize with it through the observer/observable mechanism, in this case we might be wiling to cut down on testability and allow direct link between the View and the Model.

image

Notice that the View is prompted to update its display by both Presenter (directly) and Model (via event). In most cases the view responds to simple changes in Model state and update itself, when more complex logic is involved -the Presenter takes the liberty to change the View according to application rules.    

'Passive View' will be preferred if we want to test all presentation behavior; this pattern encourages thin Views that contain no logic thus can be easily mocked and dominant Presenter that is in charge of the entire workflow (and all updates on the View) thus can be effectively tested.

image 

Notice that in case the Model is stateless (usually in web style application) it doesn't raise 'State Changed' event (as there is no state).

In ASP.NET (webforms), due to the stateless nature of the web and the unique working method of ASP.NET - it rarely makes sense to use 'Supervising Controller' as the Model is often stateless and never used as an observer, thus the only dissent alternative for webforms applications is to use 'Passive View'. From this we can conclude that if winforms Presenters/Models are to be reused in webforms application - its probably best to implement the winforms application as 'Passive View' that is applicable for both.

This article reviews the 'Supervising Controller' implementation of MVP in winforms applications and the 'Passive View' implementation in ASP.NET webforms applications. All the code in the scope of this article is available for download.

To get more background, you can read about MVP implementation in desktop applications in 'Twisting the MVC Triad', and about MVP in web applications in 'MVP for Web Applications' (for more links- see above).

MVP in .NET Winforms

Since the 'Supervising Controller' approach encourages communication between the View and the Model its best use is where the Views need to immediately synchronize with 'sudden' changes in Model data (changes which haven't been triggered by the Presenter). In such case we'll rather have the Views registered directly to Model events rather than though the Presenter, this way we won't have to make cascading changes when ever model data structure changes.

In most cases the workflow starts from the View, through the Presenter to the Model, and back to the view via the Observer/Observable mechanism. In others, the workflow starts already from the Model and to the view via the Observer/Observable mechanism.

image_thumb1 

How does it work?

1) View delegates user inputs to the Presenter -> Presenter performs some UI related calculation, changes internal UI related state, changes the View directly, and command the Model as appropriate

"in smart client applications the Model is merely a proxy for the real Model that is placed in the server"

2) Model updates its state, performs some business operation and raises the proper event -> View handles the event - it asks relevant data from the Model and changes its display.

Normally, the view creates the Presenter and they both observe and interact with some singleton Model, the latter is also being observed by other Views/Presenters of the application.

Let's examine winforms implementation for 'Add Customer' view.

image

The Model allows getting/saving data from/to the data source, it stores cached data and raises events to notify its clients about changes in its data.

// Long lasting observable with caching.
public class CustomerDao: ICustomerDao
{
    private CustomerDataMapper m_dataMapper;
    private List<Customer> m_allCustomers;
    private const string FILE_NAME = "Customers.xml";

    public event EventHandler<CustomerSavedEventArgs> CustomerSaved;

    public CustomerDao()
    {
        m_dataMapper = new CustomerDataMapper(FILE_NAME);
        m_allCustomers = m_dataMapper.GetAllCustomers();
    }

    public IEnumerable<Customer> GetAllCustomers()
    {
        return m_allCustomers;
    }

    public void Save(Customer p_customer)
    {
        m_dataMapper.Save(p_customer);
        m_allCustomers.Add(p_customer);
        raiseCustomerSaved(p_customer);
    }

    public Customer GetByName(string p_name)
    {
        foreach (Customer customer in m_allCustomers)
        {
            if (customer.ContactName == p_name)
            {
                return customer;
            }

        }

        return null;
    }

    private void raiseCustomerSaved(Customer p_customer)
    {
        if (CustomerSaved != null)
        {
            CustomerSaved(this, new CustomerSavedEventArgs(p_customer));
        }
    }
}

public class CustomerSavedEventArgs : EventArgs
{
    private readonly Customer m_customer;

    public CustomerSavedEventArgs(Customer p_customer)
    {
        m_customer = p_customer;
    }

    public Customer Customer
    {
        get { return m_customer; }
    }
}

The View creates the Presenter and injected with the Model, it delegates user inputs to the Presenter and observe the Model for changes in its data.

public partial class AddCustomerWinformsView : Form, IAddCustomerView
{
    private readonly ICustomerDao m_customerDao;
    private AddCustomerPresenter m_presenter;

    public AddCustomerWinformsView(ICustomerDao p_CustomerDao)
    {
        InitializeComponent();

        AddCustomerPresenter presenter = new AddCustomerPresenter(this, p_CustomerDao);
        m_presenter = presenter;

        m_customerDao = p_CustomerDao;
        registerToModelEvents();
    }


    public void AddCustomerToList(Customer p_customer)
    {
        ListViewItem item = new ListViewItem(p_customer.ContactName);
        item.SubItems.Add(p_customer.CompanyName);
        listView1.Items.Add(item);
    }

    public Customer CustomerToAdd
    {
        get
        {
            return new Customer(textBoxName.Text, textBoxCompany.Text);
        }
    }

    public string Message
    {
        get
        {
            return lblMessage.Text;
        }
        set
        {
            lblMessage.Text = value;
        }
    }

    private void btnAdd_Click(object sender, EventArgs e)
    {
        m_presenter.AddCustomer();
    }

    void m_customerDao_CustomerSaved(object sender, CustomerSavedEventArgs e)
    {
        AddCustomerToList(e.Customer);
    }


    private void registerToModelEvents()
    {
        m_customerDao.CustomerSaved += m_customerDao_CustomerSaved;
    }
}

The Presenter mediate between the View and the Model, it accepts gestures from the View and command the Model as appropriate.

public class AddCustomerPresenter
{
    private IAddCustomerView m_view;
    private ICustomerDao m_customerDao;

    public AddCustomerPresenter(IAddCustomerView p_view,
        ICustomerDao p_customerDao)
    {
        m_view = p_view;
        m_customerDao = p_customerDao;
    }

    public void InitView()
    {
        m_view.Message = "Use this form to add a new customer.";

        IEnumerable<Customer> customers = m_customerDao.GetAllCustomers();
        foreach (Customer customer in customers)
        {
            m_view.AddCustomerToList(customer);
        }
    }

    /// <summary>
    /// Called by the view; this grabs the updated customer from the view and commits it to the DB.
    /// </summary>
    public void AddCustomer()
    {
        if (!IsDuplicateOfExisting(m_view.CustomerToAdd))
        {
            m_customerDao.Save(m_view.CustomerToAdd);
        }
        else
        {
            m_view.Message = "The ID you provided is already in use." +
                "Please change the ID and try again.";
        }
    }

    /// <summary>
    /// Checks if a customer already exists with the same customer ID.
    /// </summary>
    private bool IsDuplicateOfExisting(Customer newCustomer)
    {
        Customer duplicateCustomer = m_customerDao.GetByName(newCustomer.ContactName);
        return duplicateCustomer != null;
    }
}

Sequence

image

Pay attention to the following:

1) The View creates the Presenter, it lives as long as it's visible to the front end user.

2) Both View and Presenter reference the Model which act as an observable. Indeed, one of the most  important principles of MVC/P is that Model should supply mechanism to allow multiple Views to observe its data.

4) The Model is unaware of nether the View nor the Presenter.

3) The presenter mediate between the View and the Model.

View and Presenter coupling

In this example the View and the Presenter are tightly coupled, the View holds reference the Presenter and access it directly and and the Presenter holds reference the View abstraction (for testing sake).  However,  there is an alternative in which only the Presenter is tightly coupled to the View while the View is not aware of the Presenter. 

You can read more about the coupling between the View and the Presenter in Model View Presenter Styles.

Smart Client Applications

MVP was designed to fit both desktop and smart-client applications (read more). In desktop applications its abstractions reside in one node (such as demonstrated above) while in smart-client web applications they scattered around two or more nodes. In most cases the View and the Presenter reside in the client and the Model is being split between the client and server [1]

MVP in ASP.NET (Browser based) Applications

In scalable browser-based web-applications, a client interact with 1..n servers where each requests coming from the client can be satisfied by a different server. 

image

ASP.NET supplies advanced framework that shields developers from dealing with client-server communication and client rendering by making the development style of web applications (webforms) look very much like the convenient style of desktop applications (winforms).

image

When developing winforms application the stating point is the Form, we start by dropping controls (widgets) on the form (view) surface, registering to the proper events and implementing the handler on the form code-behind; with ASP.NET it's pretty much the same deal, drop web control on aspx Page, register to the proper event and implement the handler on the page (view) code-behind. The big difference is that with web-forms we have to consider the stateless nature of the web, thus deal with the fact that the page is re-created on each post-back; but still, in both webforms and winforms the starting point is the view (page/form) and in both we really don't want the view to handle any kind of application/business logic.

In webforms applications - the View, the Presenter and the Model are recreated on each postback, thus MVP cannot be implemented as Supervising Controller. Instead, it's usually implemented as Passive View:

image

It goes like this: the client submits request (user press on control (e.g. button) that run-at-server), the View accept the request, it creates the Presenter and the Model and delegate the request to the Presenter, the Presenter gets current state from the View (textbox text, listbox selection etc), performs some UI related calculation, changes the View directly, and command the Model as appropriate, it than prompt the View to update its display. 

Since the Model is re-created on each postback - the Model is not an Observable by any mean; I guess that this is the biggest different between the typical winforms Model (that is state full thus can act as an observer) and the typical webforms Model.

Let's examine the webforms implementation for 'Add Customer' view.

image

The Model is passive, it allows saving customer data to DB and getting customer from DB (by ID).

// Static Dao. Recreated on each user session
public class CustomerDao: ICustomerDao
{
    private const string FILE_NAME = "Customers.xml";

    public IEnumerable<Customer> GetAllCustomers()
    {
        CustomerDataMapper dataMapper = new CustomerDataMapper(FILE_NAME);

        return dataMapper.GetAllCustomers();
    }


    public void Save(Customer p_customer)
    {
        CustomerDataMapper dataMapper = new CustomerDataMapper(FILE_NAME);
        dataMapper.Save(p_customer);
    }

    public Customer GetByName(string p_name)
    {
        CustomerDataMapper dataMapper = new CustomerDataMapper(FILE_NAME);
        IEnumerable<Customer> customers = dataMapper.GetAllCustomers();
        foreach (Customer customer in customers)
        {
            if (customer.ContactName == p_name)
            {
                return customer;
            }

        }

        return null;

    }
}

The Presenter mediate between the View and the Model, it accepts gestures from the View and command the Model as appropriate, it then prompt the View to change accordingly.

public class AddCustomerPresenter
{
    private IAddCustomerView m_view;
    private ICustomerDao m_customerDao;

    public AddCustomerPresenter(IAddCustomerView p_view,
        ICustomerDao p_customerDao)
    {
        m_view = p_view;
        m_customerDao = p_customerDao;
    }

    public void InitView()
    {
        m_view.Message = "Use this form to add a new customer.";

        IEnumerable<Customer> customers = m_customerDao.GetAllCustomers();

        foreach (Customer customer in customers)
        {
            m_view.AddCustomerToList(customer);
        }
    }

    /// <summary>
    /// Called by the view; this grabs the updated customer from the view 
    /// and commits it to the DB.
    /// </summary>
    public void AddCustomer()
    {
        Customer customerToAdd = m_view.CustomerToAdd;
        
        if (IsDuplicateOfExisting(customerToAdd))
        {
            // By passing HTML tags from the presenter to the view, 
            // we've essentially bound the presenter to an HTML context.  
            // You may want to consider alternatives to keep the 
            // presentation layer web/windows agnostic.
            m_view.Message = 
                "<span style=\"color:red\">The ID you provided is " +
                "already in use.</span> Please change the ID and try again.";

            return;
        }

        m_customerDao.Save(customerToAdd);

        m_view.AddCustomerToList(customerToAdd);
    }

    /// <summary>
    /// Checks if a customer already exists with the same customer ID.
    /// </summary>
    private bool IsDuplicateOfExisting(Customer newCustomer)
    {
        Customer duplicateCustomer = 
            m_customerDao.GetByName(newCustomer.ContactName);
        
        return duplicateCustomer != null;
    }
}

The View is attached with Presenter and Model, it respond to user events (button click) and delegate the event to the Presenter.

public partial class AddCustomerWebformsView : UserControl, IAddCustomerView
{
    private AddCustomerPresenter m_presenter;

    public void AttachPresenter(AddCustomerPresenter presenter)
    {
        m_presenter = presenter;
    }

    public void AddCustomerToList(Customer p_customer)
    {
        ListBox1.Items.Add(p_customer.ContactName);
    }

    protected void btnAdd_OnClick(object sender, EventArgs e)
    {
        // Be sure to check isPageValid before anything else
        if (!Page.IsValid)
        {
            Message = "There was a problem with your inputs." +
                " Make sure you supplied everything and try again";
            return;
        }


        m_presenter.AddCustomer();
    }

    public Customer CustomerToAdd
    {
        get
        {
            Customer customer = 
                new Customer(txtContactName.Text, txtCompanyName.Text);
            
            return customer;
        }
    }

    public string Message
    {
        set
        {
            lblMessage.Text = value;
        }
    }
}

The page is the starting point of the application. On each postback (e.g. after user click on button that is configured to run-at-browser) ASP.NET creates new instance of the page, calls Page_Load, and calls the event handler of the control that initiate the postback (e.g. btnAdd_OnClick).

public partial class AddCustomerPage : Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        CustomerDao customerDao = new CustomerDao();

        AddCustomerPresenter presenter = 
            new AddCustomerPresenter(addCustomerWebformsView, customerDao);
        
        addCustomerWebformsView.AttachModel(customerDao);
        
        if (!IsPostBack)
        {
            // No need to update the display on each post back, 
            // as ViewState travel from client to server and back 
            // from server to client.
            presenter.InitView();
        }
    }
}

Sequence

image

Comparing the Presenters (Supervising Controller vs. Passive View)

As you can see - the only difference between the Presenters is that Passive View Presenter update the view after saving the data while Supervising Controller Presenter doesn’t, the View register to the appropriate model event and update itself.

image

Source code can be downloaded from here.


[1] Even though the paper describing MVP (written by Mike Potel in 1996) mentioned that MVP model can be used for browser-based applications (where some code is downloaded for execution on the client), it doesn't really explain how and it seems like a little thought was given to the subject.