The Presenter
public class Presenter
{
public Presenter(IView view, IModel model)
{
this.view = view;
this.model = model;
}
public void InitView(bool isPostBack)
{
if(!isPostBack)
{
view.SetProducts(model.GetProducts());
}
}
public void SaveProducts(IList<IProduct> products)
{
model.SaveProducts(products);
}
}
The ASPX Page: The Starting Point
The ASPX HTML references the ASCX user control, and in code behind we have this.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
presenter = new Presenter(view,model);
view.AttachPresenter(presenter);
presenter.InitView(Page.IsPostBack);
}
The ASCX User Control
public void AttachPresenter(Presenter presenter)
{
this.presenter = presenter;
}
public void SetProducts(IList<IProduct> products)
{
// bind products to view
}
The View Interface
public interface IView
{
void AttachPresenter(Presenter presenter);
void SetProducts(IList<IProduct> products);
}
The Second Implementation
The second implementation is an event-driven approach. It uses the "view initializer and page redirector" role for the ASPX page just as the first. The ASCX user control implements a view interface that declares events that will be raised to a presenter. The view knows nothing about the presenter; it only knows how to raise events. The ASPX page initializes the presenter, passing to it the view and any model objects. The ASPX page is not responsible for attaching the presenter to a view, nor calling "InitView" on the presenter. Its only job is to wire up the presenter with the view instance and model objects, and to respond to events that the presenter might raise, such as a page redirect or some type of status event.
This example is implemented as the "Customer" module in the sample application.
Collapse
Note: The code below is used to highlight the main points of this design. Please see the sample application for a working model.
The Presenter
public class Presenter
{
public Presenter(IView view, IModel model)
{
this.view = view;
this.model = model;
this.view.OnViewLoad += new EventHandler<SingleValueEventArgs<bool>>(OnViewLoadListener);
this.view.SaveProducts += new EventHandler<SingleValueEventArgs<IList<IProduct>>>(SaveProductListener);
}
private void OnViewLoadListener(object sender, SingleValueEventArgs<bool> isPostBack)
{
if (!isPostBack.Value)
{
// Set the view for the first time
view.SetProducts(model.GetProducts());
}
}
private void SaveProductListener(object sender, SingleValueEventArgs<IList<IProduct>> products)
{
model.SaveProducts(products.Value);
}
}
The ASPX Page: The Starting Point
The ASPX HTML references the ASCX user control, and in code behind we have this.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
presenter = new Presenter(view,model);
}
The ASCX User Control
protected override void OnLoad(EventArgs e)
{
EventHandler eventHandler = OnViewLoad;
if (eventHandler != null)
{
// Invoke our delegate
eventHandler(this, new SingleValueEventArgs<bool>(Page.IsPostBack));
}
base.OnLoad(e);
}
public void SetProducts(IList<IProduct> products)
{
// bind products to view
}
protected void btnSave_Click(object sender, EventArgs e)
{
// Raise our event
OnSaveProducts(GetProducts());
}
public event EventHandler<SingleValueEventArgs<string>> SaveProducts;
public virtual void OnSaveProducts(IList<IProduct>> products)
{
EventHandler<SingleValueEventArgs<IList<IProduct>>> eventHandler = SaveProducts;
if (eventHandler != null)
{
eventHandler(this, new SingleValueEventArgs<IList<IProduct>>(products));
}
}
The View Interface
public interface IView
{
event EventHandler OnViewLoad;
event EventHandler<SingleValueEventArgs<IList<IProduct>>>SaveProducts;
void SetProducts(IList<IProduct> products);
}
The Third Implemenation
The third implementation delegates the responsibility of creating the presenter, passing in the view and model, and calling "InitView" on the presenter to the ASCX user control (view). The view has a reference to its presenter. The presenter only knows about an interface to the view. The ASPX page is used to add the user control to the page, nothing further. Since the ASPX's responsibilities from the first and second implementations now fall firmly within the responsibility of the ASCX user control, my views are easily reusable throughout my application. I can drag a user control onto a new page and with it comes its presenter, all ready to go out of the box.
This example is implemented as the "Employee" module in the sample application.
Collapse
Note: The code below is used to highlight the main points of this design. Please see the sample application for a working model.
The Presenter
public class Presenter
{
public Presenter(IView view, IModel model)
{
this.view = view;
this.model = model;
}
public void InitView(bool isPostBack)
{
if(!isPostBack)
{
view.SetProducts(model.GetProducts());
}
}
public void SaveProducts(IList<IProduct> products)
{
model.SaveProducts(products);
}
}
The ASPX Page
The ASPX HTML references the ASCX user control, nothing futher in code behind.
The ASCX User Control: The Starting Point
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
presenter = new Presenter(this,model);
presenter.InitView(Page.IsPostBack);
}
public void SetProducts(IList<IProduct> products)
{
// bind products to view
}
The View Interface
public interface IView
{
void SetProducts(IList<IProduct> products);
}
Reflecting on the Implementations
With each of the implementations, there are characteristics I favor and those I have difficulty accepting. Again, since there is no true MVP framework for ASP.NET as this time, there are no constraints forcing me to adhere to one implementation. Determining your flavor of MVP really depends on the level of separation of concerns with which you are most comfortable, and to what degree you feel your classes should be testable.
I like the use of the ASPX page as the "view initializer and page redirector" with the first two implementations. I feel that this is a well suited responsibility for the ASPX page that should not reside with the view. The view, in my opinion, should only be concerned with view-specific responsibilities. Determining what defines a view-specific responsibility is debatable and something I feel like I have wrestled with too often.
In the second implementation, I prefer how the view is ignorant of the presenter. The view is decoupled from the presenter. It only raises events, and the first event it raises, "OnViewLoad," signifies the control's loading state and passes the Page's IsPostBack value. The presenter listens for events on the view interface and commands the view to do some action when responding. The ASPX page instantiates the presenter, passing in the view instance and the model. It can register for events on the presenter as it needs.
What I do not like about the first two implementations is that since the ASPX page is involved, reusing the ASCX user control requires more work. If I want to add a user control to a different ASPX page, I now need to instantiate my model-view-presenter relationship in the new ASPX page. This becomes cumbersome when I have nested MVP relationships, where one user control may contain another user control. This dependency can be removed if I assign the responsibility of instantiating the presenter with the view and model from the ASPX page to the ASCX user control (view). As a result, the view now has more responsibility, but it is now more reusable. This added responsibility is something I may not agree with philosophically, but it helps improve usability, and my classes are still testable.
While I like the idea that my view is decoupled from the presenter in the event-driven approach, there really is no need for this separation. Using events is not always intuitive and reliable, and writing unit tests for events requires a little extra effort. There is no guarantee that the presenter subscribes to all the appropriate events on the view.
After settling my philosophical debates and finally feeling comfortable with certain responsibilities of the ASPX page, ASCX user control, and presenter in ASP.NET, I have created this third implementation. This third implementation is similar to the first implementation but it omits the role of the ASPX "view initializer and page redirector." On the positive side, my view is more reusable across my application since it is more self reliant. On the negative side, my view now has the added responsibility of creating the presenter and responding to events the presenter may raise. Even though I may feel that certain responsibilities are crossing boundaries, I keep reminding myself that this is MVP in ASP.NET - this is not a true MVC framework that enforces that good separation of concerns like Monorail.
Conclusion
MVP provides a number of advantages, but to me, the two most important are separation of concerns and testability. There is a fair amount of overhead involved in using MVP, so if you are not planning on writing unit tests, I would definitely reconsider using the pattern.
As we see with the three different implementations, there are numerous ways to implement the pattern in ASP.NET. There are even more ways than what I have chosen to display. Choose an implementation that best suits your needs. I have to work hard at implementing MVP in ASP.NET, and there are certain tradeoffs I need to be willing to accept. As long as my code is testable, reusable, maintainable, and there exists a good degree of separation of concerns, I am happy.
With Microsoft's news of releasing an MVC framework for ASP.NET, there is hope on the horizon for a framework that enforces good separation of concerns and testability. The Castle Project's Monorail is another MVC framework that I highly recommend. If you cannot wait for Microsoft's MVC framework, or do not wish to port your application to Monorail at this time, then implementing MVP could be your answer.
About the Sample Project
The sample project is written in ASP.NET 2.0 using C#. I am using the Northwind database. I am using SubSonic as my data access layer. Since SubSonic is built using the active record pattern, I do have to use interfaces in order to make my DAO classes testable. For my unit testing, I am using RhinoMocks as my mocking framework.
The sample application is comprised of five projects. The WebApp, Model, Presentation layer, Presentation.Tests, and SubSonic data access layer. This sample is simplistic and should be used as a demo. I may be doing some things in code for the sake of brevity and to simplify the concepts. This is my disclaimer for not providing "production" code with all the frameworks, tools, and layers I typically create.
History
Initial Upload: November 6, 2007.
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here