Introduction

When developing projects with ASP.NET it can be a more or less troublesome experience to test the solution being generated. What it comes down to is separation of concerns.

Developing with Webforms in ASP.NET, it allows you to define your controls and layout (possibly via CSS) using markup and events can be hooking in a code behind file. This setup might work for a lot of cases, but it is still very hard to test and you have your logic combined with your presentation.

Introducing: WebformsMVP!

WebformsMVP is a framework for helping achieve separation of your logic, data and view. The name is short for (ASP.NET) Webforms Model-View-Presenter, where the Model is your data model (i.e. a database), the View is your user controls and the Presenter is your logic. User controls define how data is displayed, and the Presenters handle the logic of interacting with the model:

ModelViewPresenter

Two important parts are the communication between View and Presenter, and Presenter and Model. What’s more important, is the lack of communication between View and Model!

Compared to Model-View-Controller the View is a bit more responsible than a normal view, in the sense that user interaction occurs in the view and the initiation also happens in the view. What Model-View-Presenter adds, is the decupling of the presenter from the button clicks etc., but it still knows about ASP.NET.

Enough introduction, let’s learn how you use it!

Open Visual Studio (I’m using 2010), and set up the project with appropriate names in the following structure (Web application, class library and MS Test project respectively):
image001

The sample project

The project I’m building is a news list creator. The purpose of the project is to list news, create, edit and delete them. I’m using a simple XML file for data, and using LINQ wherever possible. Testing is done with help from Rhino Mocks

Initial steps:

· Add WebformsMvp.dll to all projects

· Add a reference to Rhino.Mocks, System.Web and System.Web.Abstractions in the test project

In the following examples I’ll be using a class “NewsItem” with the following properties:

public class NewsItem
{
    public Guid Id { get; set; }
    public DateTime Date { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

The class is placed in a subfolder in the Logic project under “Views” called “Models”, where the namespace is WebformsMvpDemo.Logic.Views.Models. The namespace will make sense when you start using the framework for more advances scenarios.

Create the presenter first

We want to create page with a UserControl that displays all the news items created in our XML file, and create respective links to edit and delete. Just above the list, we insert a link to create a new news item.

To create our UserControl, we first create our presenter (It will all make sense in the end J). In the project WebformsMvpDemo.Logic, create a folder called “Presenters” and create a normal class called “NewsListPresenter”. This class should extend the class WebFormsMvp.Presenter with the generic parameter “IView<List<NewsItem>>”, like so:

public class NewsListPresenter : Presenter<IView<List<NewsItem>>>

In order to extend Presenter<T>, you have to implement the “ReleaseView”-method. This method will make sense in just a minute.

Next step is to implement a constructor for the “NewsListPresenter”, because the base class does not have a constructor with 0 parameters. The parameter type for the base constructor is the generic type of “IView<T>”, and in our case it is “List<NewsItem>”:

public NewsListPresenter(IView<List<NewsItem>> view, IData data, ILogging logging) : base(view)
{
    _data = data;
    _logging = logging;
 
    View.Load += View_Load;
}

I’ll explain the last parameters in the next blog post, but for now assume that some magic happens and the correct “IData” and “ILogging” parameters are passed.

The constructor code sets the local variables for a data interface (i.e. your model), a logging interface and subscribes to the views “Load” -event. This load event is available through the WebformsMVP framework, and their argument being that most data is retrieved in the load method of a Page, UserControl, etc. Next we implement the “View_Load” –method:

void View_Load(object sender, EventArgs e)
{
    try
    {
        View.Model = _data.GetNewsItems();
    }
    catch (Exception ex)
    {
        View.Model = new List<NewsItem>();
        _logging.Write("Could not get news items from data layer", ex);
    }
}

When the view loads, we want to retrieve the data to be displayed and catch any errors that might occur. It’s important to keep the views model initialized or an exception is going to be thrown when accessing it. In the catch of the data retrieval, we set the views model to en empty list and write an error to the logging framework. The “ReleaseView”-method simply releases our subscription to the views load event.

We are done implementing the presenter – next we have to test it!

Test

Wait, what? Yes, you read it right: Test Smile

I’m going to be using Rhino Mocks to help with the tests. When using Rhino, you create a test case via the Arrange, Act and Assert (AAA) methodology. We first arrange our variables, dependencies and our expectations of the test, then we act to “play” the scenario and finally we assert our expectations:

[TestMethod]
public void NewsListPresenterGetsNewsItemsFromModel()
{
    // Arrange
    var view = MockRepository.GenerateStub<IView<List<NewsItem>>>();
    var data = MockRepository.GenerateMock<IData>();
    var logging = MockRepository.GenerateMock<ILogging>();
 
    data.Stub((d) => d.GetNewsItems()).Return(new List<NewsItem>());
 
    var presenter = new NewsListPresenter(view, data, logging);
 
    // Act
    view.Raise(v => v.Load += null, view, new EventArgs());
    presenter.ReleaseView();
 
    // Assert
    Assert.IsNotNull(view.Model);
}

Our test plays the scenario when we get the data in the views load event and sets the views model.
We first arrange our view, data and logging, which are our parameters for our presenter. Next we tell the mock of the data layer, that when someone calls “GetNewsItems”, it should return a new instance of a generic list of NewsItems. We can then setup our presenter.

The presenter is created from the actual implementation, and passing the respective view, data and logging parameters for the constructor. This is the class we want to test!

Next we can act on the presenter, as if the view is instantiated and a request is made to the UserControl where the “Load”-method is called. When calling the load event, you have to pass 2 parameters; an “Object” and “EventArgs”, representing the sender and the arguments for the method. After the load event has fired, we can call the presenters “ReleaseView”-method, to remove our subscription from the views load method. This is normally done by the framework.

Last we assert, to check if everything is alright. In this case we only have to check that the views model is not null, or we would have made some mistake.

What happens if the data layer throws an exception when called? Let’s find out:

[TestMethod]
public void NewsListPresenterGetsNewsItemsFromModelTrowsException()
{
    // Arrange
    var view = MockRepository.GenerateStub<IView<List<NewsItem>>>();
    var data = MockRepository.GenerateMock<IData>();
    var logging = MockRepository.GenerateMock<ILogging>();
    var loggingMessage = "Could not get news items from data layer";
    var loggingException = new Exception();
 
    data.Stub((d) => d.GetNewsItems()).Throw(loggingException);
    logging.Expect((l) => l.Write(loggingMessage, loggingException));
 
    var presenter = new NewsListPresenter(view, data, logging);
 
    // Act
    view.Raise(v => v.Load += null, view, new EventArgs());
    presenter.ReleaseView();
 
    // Assert
    Assert.IsNotNull(view.Model);
    logging.VerifyAllExpectations();
}

Again we arrange our view, data and logging, but in this case we also arrange a logging message and an exception, which we expect to occur.

The data layer is changed, so that when we call the “GetNewsItems”-method, it throws an exception. When the data layer throws an exception, we expect that the logging framework is going to be called with the parameters of a string and the exception thrown by the data layer.

We act on the presenter, and raise the load event. We then release the view from the presenter in “ReleaseView”.

Finally we assert, and we still make sure that the views model is nut null and then we verify all the expectations on the logging mock. The “VerifyAllExpectations” is an extension method from the Rhino framework, which helps assert any expectations setup by our code. In this case we expect that the logging framework is called with a specific string and exception thrown by the data layer.

Results:

image002

Note
It’s important to note that the IData and ILogging implementations delivered to the presenter are assumed to be tested and valid. A separate project should implement and test these, where in this project it is not.

Finally – the UserControl

Create a new UserControl in a subfolder called “UserControls” and name it “NewsList” (In the web project). Here you get 3 files: NewsList.ascx, NewsList.ascx.cs and NewsList.ascx.designer.cs. Open up “NewsList.ascx.cs” and change the extended class to “MvpUserControl<List<NewsItem>>” and set a “PresenterBinding” attribute on top of the class with the argument “typeof(NewsListPresenter)” as its argument:

[PresenterBinding(typeof(NewsListPresenter))]
public partial class NewsList : MvpUserControl<List<NewsItem>>
{
}

This is your code behind file, which only needs other methods if any events or custom databing needs to occur.

Note
The “PresenterBinding” attribute is part of the WebformsMVP framework, but is supposed to disappear in a future version. The attribute tells the framework which presenter to use, and in future versions it will be discovered automatically via a naming convention as seen in ASP.NET MVC.

Using the class “MvpUserControl<T>” as the extended class compared to the normal “UserControl” from ASP.NET gives us access to the model delivered by the presenter. The generic argument of “MvpUserControl” is the type of the model shared. In our news list case, the model is a list of news items. We can access the model via the property “Model”, and use it in our markup – open the “NewsList.ascx” file:

<a href="AddNewsItem.aspx">Add new news item</a><br />
<asp:Repeater runat="server" DataSource="<%# this.Model %>">
    <HeaderTemplate>
        <table cellpadding="2" cellspacing="0" border="1">
    </HeaderTemplate>
    <ItemTemplate>
        <tr>
            <td>
                <h3>
                    <%
   1: # Server.HtmlEncode(((WebformsMvpDemo.Logic.Views.Model.NewsItem)Container.DataItem).Title) 
%></h3>
                <p class="newsDate">
                    <%
   1: # ((WebformsMvpDemo.Logic.Views.Model.NewsItem)Container.DataItem).Date.ToString("dd/MM-yyyy hh:mm")
%></p>
                <p>
                    <%
   1: # Server.HtmlEncode(((WebformsMvpDemo.Logic.Views.Model.NewsItem)Container.DataItem).Content).Replace("\n", "<br />")
%></p>
            </td>
            <td>
                <a href="EditNewsItem.aspx?id=<%# ((WebformsMvpDemo.Logic.Views.Model.NewsItem)Container.DataItem).Id.ToString("N") %>">
                    Edit</a> | <a href="DeleteNewsItem.aspx?id=<%# ((WebformsMvpDemo.Logic.Views.Model.NewsItem)Container.DataItem).Id.ToString("N") %>">
                        Delete</a>
            </td>
        </tr>
    </ItemTemplate>
    <FooterTemplate>
        </table>
    </FooterTemplate>
</asp:Repeater>

The markup contains a link to create a new news item and a Repeater that displays the news items. The Repeater has its datasouse set to “this.Model”, since “this.Model” is our list of news items. For each item in the model, a table row is created with two columns. The first column contains the content of the news item, with title, date and content, and the second contains links to edit and delete for the respective news item presented. What’s missing here is the strongly typed items in the repeater, so a cast has to be made from the “Container.DataItem” and then the properties can be accessed.

Conclusion

WebformsMVP delivers a way to separate and test logic from presentation of data. UserControls can layout and present data via styles and so forth, but the problem is usually the logic.

Next blog post will contain more information regarding testing of more advanced presenters.

Code will be available in a later blog post.

Links