Created On:
15/03/2007
Author:
Aaron Robson
Tags:
aspnet, TDD, Model View Presenter, MVP, PoCoRail, PoCoBits, PoCoPersist

Asp.net Model View Presenter example using PoCoRail

Update: 8th Sept 07:

The version discussed in this article is somewhat outdated, although still mostly relevant.
A newer version is available here.


Update: 19th July 07:

Realized this article is getting a bit of traffic due to being 'kicked' (thanks - I usually have to do it myself :), and wanted to let people know that work has been ongoing, and PoCoRail is at least 50 gazillion times better now! If anyone feels like being an 'early adopter', drop me an email or comment and I'll hook you up.

Aaron.


PoCoRail is a little bit of free software from the PoCoBits family created to ease and structure the development of asp.net web applications. It aims to help developers concentrate on the domain model and presentational logic involved in their application, enabling a clear separation between these layers and the asp.net user interface.

It doesn't require any changes to the fundamental templating capabilities of asp.net, but due to the Model View Presenter style, development naturally moves towards a thinner and more maintainable user interface layer. This approach simplifies unit testing and Test Driven Development, but also gives a little bit of structure to your applications. Our applications usually have little or no need for many of the higher level abstractions provided by Web Forms such as the GridView or DataGrid, however no restriction is imposed by use of PoCoRail.

The vision which inspired PoCoRail is of dynamic asp.net web sites with an emphasis on clean semantic markup, CSS and Unobtrusive Javascript. All of this can be achieved while still having access to the fantastic flexibility and power provided by asp.net and the dotnet framework.

Basic PoCoRailing

In its most basic usage, PoCoRail takes care of connecting your View controls with the appropriate Presenters and gives you strongly typed access to the Presenter so that you can call methods in response to Asp.net events. To get this functionality, you inherit your Views from PoCoRailUserControlView (or PoCoRailControlView etc) and inherit your Presenters from PoCoRailPresenter. You then register your Presenters with the PresentationManager by a call such as

PresentationManager.AddPresenters(Assembly.GetExecutingAssembly());

Once you've placed your View onto a Page, you could respond to events in CodeBehind by simply delegating to the Presenter from within your Views event handler. We recommend you use more advanced PoCoRailing as discussed below, but here is an example of the basics.

void AddTagBtn_Click(object sender, EventArgs e)
{
int articleId;
if (int.TryParse(Request.QueryString.Get(1), out articleId))
{
Presenter.Create(this, articleId);
}
}

This alone provides good separation and allows us to work in the MVP style, but we can make life even better by having PoCoRail automatically instantiate our Views and call methods on our Presenter in response to Actions determined from the Request Url. To get this level of functionality, all you need to do is add a PoCoRailPlaceHolder control onto your Page (or MasterPage).

Advanced PoCoRail gives you some REST

Working in a RESTful fashion is one way we can add some process and structure to our web development. If possible, we don't want to go too far from the concepts defined in Asp.net, so we use standard Pages to represent resources, and Actions on these resources are indicated by parameters passed in the Url or stored in the CommandName argument of PostBack controls such as Button. Each Page (resource) may support several Actions and Views which implement the user interface required to represent those actions.

Each Presenter can respond to one or more Actions. These Actions are either encoded in the Url and invoked automatically by PoCoRail, or are invoked explicitly by a containing Presenter. Common Actions are List, Show, New, Create, Edit, Update and Destroy. Actions can be split into what we will call GET actions and POST actions, and often come in pairs or triplets. For example - Edit (GET) and Update (POST) or New (GET) and Create (POST). GET actions are not intended to change data on the server side and are referenced by a Url. POST actions may result in data being created, modified or destroyed. [We haven't concerned PoCoRail with dispatching the other intricacies of Http (PUT, DELETE) or restricted the set of Actions a presenter can implement - its up to you how RESTful you want to be. Dare we say Lo-REST.]

An example Url would look like: http://intrepidnoodle.com/article/edit/1.aspx. This describes the resource - article, the action - edit, and the id of the article we want to edit - 1. This is assuming some level of url rewriting (which we fully support within PoCoRail using [a slightly modified version of] UrlRewriting.Net). Without Url Rewriting it would look like http://intrepidnoodle.com/article?m=edit&qp0=1. In the example, we haven't included any rewriting, just to simplify the web.config.

Without going into much further detail about PoCoRail, we'll show you the sort of code you can expect to be working with...

Model View Presenter Example

A good example of using MVP with asp.net was created by Phil Haack so instead of creating a new one, we'll show how it could be implemented using PoCoRail. Briefly, the example is of a single page which allows us to Create Articles (blog posts) and to be able to Add and Delete Tags to / from that Article. Its worth reading that article first, along with the comments to get a feel for the whole thing if you're not familiar with MVP.

PoCoRail Solution Explorer

A working Visual Studio 2005 solution with all of the required PoCoBits is provided for your delectation. We use the Web Application Project model for Visual Studio, although this isn't a requirement. Our tests use Ayende's (Oren's) latest RhinoMocks 3.0 Beta4 and NUnit 2.4 RC.

The solution is split into separate layers.

User Interface:
WebPoCoBits
Presentation Logic:
WebPoCoBitsPresentation
Domain Model:
WebPoCoBitsModel
Repositories / DataAccess:
WebPoCoBitsRepos
Tests:
WebPoCoBitsTests

Test Driving the Presenters

So that we don't need a hardback cover for this article, most of the tests have been ommitted, but interesting issues have been highlighted.

From the requirements, we know we're acting on two different entities in the domain model - we'll create a Presenter for Article, and one for Tag

ArticlePresenter class declaration

PoCoRail requires Presenter classes to inherit from PoCoRailPresenter, and we also extract an interface IArticlePresenter to use during View creation.

public class ArticlePresenter : PoCoRailPresenter, IArticlePresenter

Article Presenter Constructors

Below are the ArticlePresenter Constructors. We won't discuss the IPoCoRailWebContext parameter in this article, as its not needed to implement the functionality in this example, however it contains methods to enable us to generate Urls, Redirect, access Session State, Cache etc. It is simply mocked in the tests, and has no expectations set on it.

/// <summary>
/// The default PresenterFactory uses this constructor, so the
/// concrete implementation for IArticleRepository must be retrieved
/// using a Service Locator.
/// </summary>
/// <param name="webContext"></param>
public ArticlePresenter(IPoCoRailWebContext webContext) : base(webContext)
{
articleRepo = new StaticArticleRepository();
}

/// <summary>
/// This can be used purely for testing purposes, or alternatively dependency injection
/// can be used to get the repository when creating the presenter if providing your
/// own implementation of PresenterFactory.
/// </summary>
/// <param name="webContext"></param>
/// <param name="articleRepo"></param>
public ArticlePresenter(IPoCoRailWebContext webContext, IArticleRepository articleRepo) : base(webContext)
{
this.articleRepo = articleRepo;
}

Testing the Article Edit Action

The initial tests on each Presenter just check that the required Action interacts with the View correctly. Below is a test for the Edit Action of an Article. We create a mock view, an ArticlePresenter and a test Article. We then set up some expectations on the view.

When the article presenter executes the Edit Action, we expect the Tag Presenter to run its List Action so that we can see the tags on the article. One of the expectations verifies that the Tag component has the List Action called on its Presenter. The actual tests for the TagPresenter go into further detail testing the actual List Action, so here we just check that it's actually called.

[Test]
public void PresenterEditActionSetsViewData()
{
IArticlePresenter presenter = CreatePresenter();

Article article = CreateArticle();

IArticleEditView view = CreateMockArticleEditView();

CreateEditViewExpectations(view, article);

mocks.ReplayAll();

presenter.Edit(view, article);

mocks.VerifyAll();
}
private void CreateEditViewExpectations(IArticleEditView view, Article article)
{
view.ArticleTitle = article.Title;
view.ArticleBody = article.Body;

ITagPresenter mockTagPresenter = CreateMockTagPresenter();

ITagView mockTagListView = CreateMockTagListView();

PoCoRailComponent<ITagView, ITagPresenter> component =
new PoCoRailComponent<ITagView, ITagPresenter>(mockTagListView, mockTagPresenter);

Expect.Call(view.TagComponent).Return(component);

component.Presenter.List(component.View, article);
}

After testing the Edit and Update Article Actions interact with the View correctly, we test that the relevent data is retrieved via the Repositories when given the appropriate Id's. To do this, we pass a Mock Repository to the constructor of ArticlePresenter. Repository interfaces are either retrieved using a Service Locator during construction, or are injected via Constructor during Presenter Creation. In the example, we're using a very naive static repository. PoCoRail provides extension points for you to implement the constructor injection should you wish to use it (see PoCoRailManager).

Article Edit Action

Here we can see the implementation required to pass the tests for the Edit Action.

/// <summary>
/// This will get automatically called if there is no other Edit Method defined
/// which takes the correct type and number of parameters as passed in the Url.
/// This allows you to manually retrieve values from the query string.
/// </summary>
/// <param name="view"></param>
public void Edit(IArticleEditView view)
{
// Just an example
}

/// <summary>
/// PoCoRail will automatically call this Method if the QueryString contains
/// one parameter which can be converted to an int.
/// </summary>
/// <param name="view"></param>
/// <param name="articleId"></param>
public void Edit(IArticleEditView view, int articleId)
{
Article article = articleRepo.Find(articleId);

Edit(view, article);
}

/// <summary>
/// This Method is delegated to by other Edit Methods, and is also useful
/// should the Article Edit View end up as a Component of another View / Presenter
/// - the containing Presenter could delegate to this Method.
/// </summary>
/// <param name="view"></param>
/// <param name="article"></param>
public void Edit(IArticleEditView view, Article article)
{
if (!IsPostBack)
{
view.ArticleTitle = article.Title;
view.ArticleBody = article.Body;

IPoCoRailComponent<ITagView, ITagPresenter> component = view.TagComponent;

component.Presenter.List(component.View, article);
}
}

Action Interception

One feature of PoCoRail to see here is the Tag Action Interception shown below. By creating methods which take an ITagView parameter along with an IArticleEditView parameter, we can carry out any processing required by the ArticlePresenter before the TagPresenter has its Action called during a PostBack Action. In this case, we just want to make sure we Update the Article in case we'd made changes to the document before pressing the button to Add or Delete a Tag.

public void Destroy(IArticleEditView parentView, ITagView view, int articleId, int tagToDestroyId)
{
Update(parentView, articleId);
}

public void Create(IArticleEditView parentView, ITagView view, int articleId)
{
Update(parentView, articleId);
}

The TagPresenter doesn't do anything special, so we won't show it here. The same goes for the Article and Tag Models, which are fairly basic.

The tests we created ended up driving the interfaces we need to implement on the Views.

ArticlePresenter Interface

Passed as a generic parameter when defining the ArticleEditView.

public interface IArticlePresenter : IPoCoRailPresenter
{
void Edit(IArticleEditView view, Article article);
void Update(IArticleEditView view, Article article);

void Edit(IArticleEditView view, int articleId);
void Update(IArticleEditView view, int articleId);
}

ArticleEditView Interface

Implemented by the appropriate View.

public interface IArticleEditView
{
string ArticleTitle { get; set; }
string ArticleBody { get; set; }

IPoCoRailComponent<ITagView, ITagPresenter> TagComponent { get; }
}

TagPresenter Interface.

Passed as a generic parameter when defining the TagView, also useful for Mocking during certain tests.

public interface ITagPresenter : IPoCoRailPresenter
{
Tag Create(ITagView view, Article article);
void List(ITagView view, Article article);
void Destroy(ITagView view, Article article, Tag tagToDestroy);

Tag Create(ITagView view, int articleId);
void List(ITagView view, int articleId);
void Destroy(ITagView view, int articleId, string tagToDestroyName);
}

TagView Interface.

Implemented by the appropriate View.

public interface ITagView
{
IList<Tag> Tags { set; }
string TagName { get; }
}

Creating the Views

We'll create our views from UserControls, as that's the simplest way to do it. Depending on how we might want to reuse these later, we could decide to create them as custom controls. Two separate Views are created named TagView and ArticleEditView. So that PoCoRail can automatically instantiate our Views, we have to use a specific naming convention for View controls in the form of Page-Action-View - so ArticleEditView will automatically be instantiated for us within the Article Page. As it contains the TagView as a child control, we refer to TagView as a component and its naming is not as important thus we just called it TagView. A Component isn't dynamically loaded by PoCoRail, but rather is explicitly placed in a containing view.

The ArticleEdit UserControl

<%@ Register Src="../ViewComponents/TagView.ascx" TagName="TagView" TagPrefix="viewComps" %>
<fieldset id="editArticle">
<legend>Edit Article</legend>
<asp:Label ID="articleTitleLbl" AssociatedControlID="articleTitleTb" runat="server">Title</asp:Label><asp:TextBox ID="articleTitleTb" runat="server" />
<asp:Label ID="articleBodyLbl" AssociatedControlID="articleBodyTb" runat="server">Body</asp:Label><asp:TextBox ID="articleBodyTb" TextMode="MultiLine" Rows="15" Columns="40" runat="server" />
<asp:Button ID="updateArticleBtn" runat="server" Text="Update" CommandName="Update" />
</fieldset>
<viewComps:TagView ID="tagListView" runat="server" />

ArticleEditView CodeBehind

using PoCoRail.Interfaces;
using PoCoRail.Web.Controls;
using WebPoCoBitsPresentation;

namespace WebPoCoBits.Views
{
public partial class ArticleEditView : PoCoRailUserControlView<IArticlePresenter>, IArticleEditView
{
public string ArticleTitle
{
get { return articleTitleTb.Text.Trim(); }
set { articleTitleTb.Text = value; }
}

public string ArticleBody
{
get { return articleBodyTb.Text.Trim(); }
set { articleBodyTb.Text = value; }
}

public IPoCoRailComponent<ITagView, ITagPresenter> TagComponent
{
get { return tagListView.AsComponent<ITagView>(); }
}
}
}

The TagView UserControl

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TagView.ascx.cs" Inherits="WebPoCoBits.ViewComponents.TagView" %>
<fieldset id="tags">
<legend>Tags</legend>
<asp:Repeater ID="tagRptr" Visible="false" runat="server">
<HeaderTemplate><ul></HeaderTemplate>
<ItemTemplate><li><%# Eval("Name") %><asp:Button ID="destroyTagBtn" Text="X" CommandName="Destroy" CommandArgument='<%# Eval("Name") %>' runat="server" /></li></ItemTemplate>
<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
<div>
<asp:Label ID="tagNameLbl" AssociatedControlID="tagNameTb" runat="server">Tag Name</asp:Label><asp:TextBox ID="tagNameTb" runat="server" />
<asp:Button ID="addTagBtn" runat="server" Text="Add" CommandName="Create" />
</div>
</fieldset>

TagView CodeBehind

using System.Collections.Generic;
using PoCoRail.Web.Controls;
using WebPoCoBitsModel;
using WebPoCoBitsPresentation;

namespace WebPoCoBits.ViewComponents
{
public partial class TagView : PoCoRailUserControlView<ITagPresenter>, ITagView
{
public string TagName
{
get { return tagNameTb.Text.Trim(); }
}

public IList<Tag> Tags
{
set
{
tagRptr.Visible = true;
tagRptr.DataSource = value;
tagRptr.DataBind();
}
}
}
}

The views are thankfully very simple - they inherit from PoCoRailUserControl, pass the appropriate presenter interface as a generic parameter and implement the appropriate View interface. The only thing to highlight is that the Buttons hold the Action to execute on the presenter within their CommandName attribute, and where needed, the CommandArgument is used and this value gets passed as the last parameter to the Presenter Method invoked. For instance the TagPresenter Destroy Method is shown below, where the articleId comes from the QueryString, and the tagToDestroyName comes from the CommandArgument of the destroyTagBtn.

void Destroy(ITagView view, int articleId, string tagToDestroyName);

Due to the use of a Supervising Controller model of MVP, we use basic DataBinding and a Repeater control to deal with the display of the Tag list. We pay for this simplicity by having to reference the Model assembly from our Web User Interface, but this is a trade off we're happy to make - and you're free to choose Passive View if you prefer.

Editing an Article

Above is the final view we get in our browser - not a masterpiece yet, but with some good functionality implemented very easily.

Conclusion

If you're interested in using MVP as a way to develop your asp.net projects, we hope that this example of using PoCoRail will interest you enough to Download the Visual Studio solution and have a play. PoCoRail is still young (meaning beta), but feel free to reuse the software as you wish* - we're hoping for some feedback to help take off the rough edges. [*Don't redistribute or use to create competing products.]

Some older and more mature relatives of PoCoRail (distant cousins thrice removed) are of course MonoRail which is part of the Castle Project, and the eldest of them all Ruby on Rails. Perhaps one day we'll grow up to be just like MonoRail - that wouldn't be a bad thing by any means - but for now we like to think we do things a little bit differently (it might just be youthful rebellion!). However, if you're beginning an asp.net project and can't wait long enough for us to grow up we recommend you take a look at MonoRail, it may save your life :)

Comments

Riccardo -
Hi, I'd be inserested in testing latest bits of code. Thanks Riccardo
jafin -
Hi Aaron, I'd be interested in seeing how you've progressed since this article as well, are you in a public repo like googlecode/codeplex with this? --jafin.