In the first blog post, we covered the basics of creating a user control with WebformsMVP. In this blog post we’ll look at another example: Deletion of a news item. The example might be simple to understand, but there is some logic to cover. Our frontpage displayed a list of news items with respective edit and delete links, which only link to another page. From EditNewsItem.aspx we can edit a news item and from DeleteNewsItem.aspx we can delete a news item – it’s a confirmation page…

Remember: Presenter first

Our first step is to create our presenter: “NewsItemDeletePresenter”:

public class NewsItemDeletePresenter : Presenter<INewsItemDelete>

The difference to our first presenter is the generic parameter for the base presenter class, in this case an interface called “INewsItemDelete”, which looks like this:

public interface INewsItemDelete : IView<NewsItem>
{
    event Action<NewsItem> NewsItemDeleting;
}

This interface inherits from “IView<NewsItem>”, which would be a normal approach to using presenters. We want our model to be a “NewsItem”, but we also want a possibility to act when a button is clicked to delete the news item. Hence we add an event to the interface between the presenter and view, called “NewsItemDeleting”. The type of event is an “Action<NewsItem>”, which gives us a prototype of a receiving method like so:

void View_NewsItemDeleting(NewsItem newsItem)

So, creating our constructor looks like this:

public NewsItemDeletePresenter(INewsItemDelete view, IData data, ILogging logging)
    : base(view)
{
    _data = data;
    _logging = logging;
 
    View.Load += View_Load;
    View.NewsItemDeleting += View_NewsItemDeleting;
}
 
public override void ReleaseView()
{
    View.Load -= View_Load;
    View.NewsItemDeleting -= View_NewsItemDeleting;
}

We set our data and logging variables and listen to both the view’s “Load” and “NewsItemDeleting” events. The “ReleaseView” is also shown here, since it is rather simple – we clean up our event subscriptions.

Next we implement the 2 methods for our event subscription. The “View_Load” first.

Load

void View_Load(object sender, EventArgs e)
{
    Guid newsId;
    if (!Guid.TryParse(this.Request["id"], out newsId))
    {
        this.Response.Redirect("default.aspx", true);
        return;
    }
 
    var newsItem = _data.GetNewsItem(newsId); 
    if (newsItem == null)
    {
        this.Response.Redirect("default.aspx", true);
        return;
    }
    View.Model = newsItem as NewsItem;
}

Our first step is to retrieve the id from the query string and parse it to a valid “Guid” object. If this is not possible, we cannot find it in the data layer. This means we should redirect back to “default.aspx”, if no valid id can be found.

What you’ll notice here is the “this.Request” and “this.Response” property provided by base Presenter class. You have access to both “Cache”, “HttpContext”, “RouteData”, “Request”, “Response”, “Server” and a couple other properties, which enables you to get URL strings and info, redirect, get server values, etc. What’s missing is the “Session” property, which is on purpose. The argument being from the framework builder’s is, that sessions don’t scale (another discussion, but not in this blog series).

If we get a valid “Guid” from the url parameter “id”, we retrieve the news item from the data layer and check if it returns a “null”-value. If the value is “null”, we also redirect back, because a valid “Guid” id could be passed to the page, but that does not mean it exist in the data layer.

Lastly we set the views mode to our retrieved news item.

Load Test

We will not be performing a load test, but we will test the load method!

Our first goal is to test the first portion of the method, which checks for a valid “Guid” via the “this.Requst” property. This test shows the power of WebformsMVP, since it allows us to abstracts the webserver away. With Rhino, we can mock the “HttpContext”, “HttpRequest” and “HttpResponse” like so (We are still using the Arrange, Act and Assert (AAA) method):

[TestMethod]
public void ViewLoadParsesIdButCannotParseGuidFromRequest()
{
    // Arrange
    var view = MockRepository.GenerateStub<INewsItemDelete>();
    var data = MockRepository.GenerateMock<IData>();
    var logging = MockRepository.GenerateMock<ILogging>();
    var httpContext = MockRepository.GenerateMock<HttpContextBase>();
    var httpRequest = MockRepository.GenerateMock<HttpRequestBase>();
    var httpResponse = MockRepository.GenerateMock<HttpResponseBase>();
 
    var requestEntry = "id";
    var requestValue = "sdf34rsdfsdgcfg3j";
    var redirectUrl = "default.aspx";
 
    httpRequest.Stub(r => r[requestEntry]).Return(requestValue);
    httpResponse.Expect(r => r.Redirect(redirectUrl, true));
    httpContext.Stub(h => h.Request).Return(httpRequest);
    httpContext.Stub(h => h.Response).Return(httpResponse);
 
    var presenter = new NewsItemDeletePresenter(view, data, logging)
    {
        HttpContext = httpContext
    };
 
    // Act
    view.Raise(v => v.Load += null, view, new EventArgs());
    presenter.ReleaseView();
 
    // Assert
    httpResponse.VerifyAllExpectations(); // Expect redirect
}

We setup view, data and logging, and we setup both “HttpContext”, “HttpRequest” and “HttpResponse” using a mock of the base classes “HttpContextBase”, “HttpRequestBase” and “HttpResponseBase”. These classes are provided by the .NET framework in “System.Web.Abstractions”. We will see their use in just a minute.

Next we define our request entry and value and redirect URL. This is to simplify the reading of the code, but it also makes it possible in other scenarios to assert what actually happened.

Lastly we arrange what should happen, so we create the “httpRequest” mock, and instruct it to return our “requestValue”, if someone accesses the entry “requestEntry”. This means that for future references of “httpRequest[‘id’]”, the object returns “sdf34rsdfsdgcfg3j”.
For the “httpResponse” object, we expect that the redirect method is going to be called with the “redirectURL” and “true” as parameters, because that the presenter cannot validate the “requestValue” as a “Guid”.
Last we setup the “httpContext” to return “httpRequest” and “httpResponse” when accessing their respective properties on the object.

We can now create our presenter, and set is “HttpContext” to the mocked context we just created.

Now we “replay” the scenario by raising the view’s load event and supplying the parameters. Afterwards we release the view and we can assert our expectations.
In this scenario, we only expect the “httpResponse” object to have its “Redirect” method called with certain properties. If this does not happen, the test will fail.

The next test tests almost the same thing when retrieving a news item from the datalayer. We expect a redirect if no news item can be found, but I won’t show this here, you can look in the code.

What’s more interesting is that I have actually not coded the method very well. Take a look at this test:

[TestMethod]
public void ViewLoadParsesIdButDataLayerThrowsException()
{
    // Arrange
    var view = MockRepository.GenerateStub<INewsItemDelete>();
    var data = MockRepository.GenerateMock<IData>();
    var logging = MockRepository.GenerateMock<ILogging>();
    var httpContext = MockRepository.GenerateMock<HttpContextBase>();
    var httpRequest = MockRepository.GenerateMock<HttpRequestBase>();
    var httpResponse = MockRepository.GenerateMock<HttpResponseBase>();
 
    var requestEntry = "id";
    var requestValue = "E4B202593D9E4D8EA16A02091EBC439F";
    var redirectUrl = "default.aspx";
    var newsId = Guid.Parse(requestValue);
    var dataLayerException = new Exception();
 
    data.Stub(d => d.GetNewsItem(newsId)).Throw(dataLayerException);
 
    httpRequest.Stub(r => r[requestEntry]).Return(requestValue);
    httpResponse.Expect(r => r.Redirect(redirectUrl, true));
    httpContext.Stub(h => h.Request).Return(httpRequest);
    httpContext.Stub(h => h.Response).Return(httpResponse);
 
    var presenter = new NewsItemDeletePresenter(view, data, logging)
    {
        HttpContext = httpContext
    };
 
    // Act
    view.Raise(v => v.Load += null, view, new EventArgs());
    presenter.ReleaseView();
 
    // Assert
    httpResponse.VerifyAllExpectations(); // expect redirect
}

It’s almost the same, but includes a call to the data layer, where it throws an exception. This gives me the following error message when running the test: “WebformsMvpDemo.Logic.Tests.NewsItemDeletePresenterTests.ViewLoadParsesIdButDataLayerThrowsException threw exception: ...”
We better “try-catch” the call to the data layer – new “View_Load” method:

void View_Load(object sender, EventArgs e)
{
    Guid newsId;
    if (!Guid.TryParse(this.Request["id"], out newsId))
    {
        this.Response.Redirect("default.aspx", true);
        return;
    }
 
    NewsItem newsItem = null;
    try
    {
        newsItem = _data.GetNewsItem(newsId); // Do not set View.Model directly if you test for View.Model == null. When View.Model is null the framwork throws an exception
    }
    catch (Exception ex)
    {
        newsItem = null;
        _logging.Write(string.Format("Could not retrieve news item for id: '{0}'.", newsId.ToString("N")), ex);
    }
     
    if (newsItem == null)
    {
        this.Response.Redirect("default.aspx", true);
        return;
    }
    View.Model = newsItem;
}

Much better, and the test does not fail!

The last test before user interaction, where everything works (a sunshine scenario), will not be written here – look in the code Smile.

Deletion of news item

Next up, the “View_NewsItemDeleting” method. Our event prototype defines a method with a single parameter, a “NewsItem”. We need to implement a method that handles the deletion of the particular news item, and then redirect back to the news list:

void View_NewsItemDeleting(NewsItem newsItem)
{
    try
    {
        _logging.Write(string.Format("Deleting news item with id: '{0}'.", newsItem.Id.ToString("N")));
        _data.DeleteNewsItem(newsItem);
        _logging.Write(string.Format("News item with id: '{0}', was deleted.", newsItem.Id.ToString("N")));
    }
    catch (Exception e)
    {
        _logging.Write(string.Format("Could not delete news item with id: '{0}'", newsItem.Id.ToString("N")), e);
    }
    this.Response.Redirect("default.aspx");
}

This method could do some more stuff, like showing an error message if the news item cannot be deleted etc., but for simplicity the method is kept short.

Delete test

The test is rather straightforward and is composed of 2 test methods. The first presented is where the data layer throws an exceptions:

[TestMethod]
public void ViewUpdatesNewsButDataLayerTrowsException()
{
    // Arrange
    var view = MockRepository.GenerateStub<INewsItemDelete>();
    var data = MockRepository.GenerateMock<IData>();
    var logging = MockRepository.GenerateMock<ILogging>();
    var httpContext = MockRepository.GenerateMock<HttpContextBase>();
    var httpRequest = MockRepository.GenerateMock<HttpRequestBase>();
    var httpResponse = MockRepository.GenerateMock<HttpResponseBase>();
 
    var requestEntry = "id";
    var requestValue = "4c278cea5c2145d395a64a9188d960c4";
    var redirectUrl = "default.aspx";
    var newsId = Guid.Parse(requestValue);
    var newsItemFromDb = new NewsItem { Id = newsId, Date = DateTime.Now, Title = "News item 1", Content = "Hello world" };
    var newsItemFromView = new NewsItem { Id = newsId, Date = DateTime.Now, Title = "News item 1-2", Content = "Hello world3" };
    var exception = new Exception();
    var loggingStartMessage = string.Format("Deleting news item with id: '{0}'.", newsItemFromView.Id.ToString("N"));
    var loggingExceptionMessage = string.Format("Could not delete news item with id: '{0}'", newsItemFromView.Id.ToString("N"));
 
    data.Stub(d => d.GetNewsItem(newsId)).Return(newsItemFromDb);
    data.Stub(d => d.DeleteNewsItem(newsItemFromView)).Throw(exception);
    logging.Expect(l => l.Write(loggingStartMessage));
    logging.Expect(l => l.Write(loggingExceptionMessage, exception));
    httpRequest.Stub(r => r[requestEntry]).Return(requestValue);
    httpResponse.Expect(r => r.Redirect(redirectUrl));
    httpContext.Stub(h => h.Request).Return(httpRequest);
    httpContext.Stub(h => h.Response).Return(httpResponse);
 
    var presenter = new NewsItemDeletePresenter(view, data, logging)
    {
        HttpContext = httpContext
    };
 
    // Act
    view.Raise(v => v.Load += null, view, new EventArgs());
    view.Raise(v => v.NewsItemDeleting += null, newsItemFromView);
    presenter.ReleaseView();
 
    // Assert
    httpResponse.VerifyAllExpectations(); // expect redirect
    logging.VerifyAllExpectations(); // expect logging
}

We use the items from arrange again and add an exception and logging message, since the datalayer throws an exception when calling “DeleteNewsItem”.

In the act-part, we raise both the views “load” and “NewsItemDeleting” events, to initiate the deletion of the news item.

Lastly we assert that a redirect has been started and that the logging has occurred.

A success scenario is described in the last test:

[TestMethod]
public void ViewDeletesNewsAndSucceeds()
{
    // Arrange
    var view = MockRepository.GenerateStub<INewsItemDelete>();
    var data = MockRepository.GenerateMock<IData>();
    var logging = MockRepository.GenerateMock<ILogging>();
    var httpContext = MockRepository.GenerateMock<HttpContextBase>();
    var httpRequest = MockRepository.GenerateMock<HttpRequestBase>();
    var httpResponse = MockRepository.GenerateMock<HttpResponseBase>();
 
    var requestEntry = "id";
    var requestValue = "4c278cea5c2145d395a64a9188d960c4";
    var redirectUrl = "default.aspx";
    var newsId = Guid.Parse(requestValue);
    var newsItemFromDb = new NewsItem { Id = newsId, Date = DateTime.Now, Title = "News item 1", Content = "Hello world" };
    var loggingDebugBeginMessage = string.Format("Deleting news item with id: '{0}'.", newsItemFromDb.Id.ToString("N"));
    var loggingDebugEndMessage = string.Format("News item with id: '{0}', was deleted.", newsItemFromDb.Id.ToString("N"));
 
    data.Stub(d => d.GetNewsItem(newsId)).Return(newsItemFromDb);
    logging.Expect(l => l.Write(loggingDebugBeginMessage));
    logging.Expect(l => l.Write(loggingDebugEndMessage));
    httpRequest.Stub(r => r[requestEntry]).Return(requestValue);
    httpResponse.Expect(r => r.Redirect(redirectUrl));
    httpContext.Stub(h => h.Request).Return(httpRequest);
    httpContext.Stub(h => h.Response).Return(httpResponse);
 
    var presenter = new NewsItemDeletePresenter(view, data, logging)
    {
        HttpContext = httpContext
    };
 
    // Act
    view.Raise(v => v.Load += null, view, new EventArgs());
    view.Raise(v => v.NewsItemDeleting += null, newsItemFromDb);
    presenter.ReleaseView();
 
    // Assert
    httpResponse.VerifyAllExpectations(); // Expect redirect
    logging.VerifyAllExpectations(); // verify logging messages
}

We now have tests that verify what we are doing in the presenter is valid. Next step is to create the actual UserControl – create a new UserControl called “NewsItemDelete” and open it’s code behind file. Here we change the inheritance from a “UserControl” to “MvpUserControl<NewsItem>” and also defines that we want to implement our extended view interface in “INewsItemDelete”. This gives us an implementation of our UserControl like so:

[PresenterBinding(typeof(NewsItemDeletePresenter))]
public partial class NewsItemDelete : MvpUserControl<NewsItem>, INewsItemDelete
{
    protected void DeleteNewsItem(object sender, EventArgs e)
    {
        if (NewsItemDeleting != null)
        {
            NewsItemDeleting(this.Model);
        }
    }
 
    #region INewsItemDelete Members
 
    public event Action<NewsItem> NewsItemDeleting;
 
    #endregion
}

Again, the “PresenterBinding”-attribute should be overlooked for now, but we assign a “NewsItemDeletePresenter” to use as our presenter. The “DeleteNewsItem” is an eventhandler for a “Button” in the markup:

<table cellpadding="2" cellspacing="0">
    <tr>
        <td>Id:</td>
        <td><%
   1: : this.Model.Id.ToString("N") 
%></td>
    </tr>
    <tr>
        <td>Title:</td>
        <td><%
   1: : this.Model.Title 
%></td>
    </tr>
    <tr>
        <td>Date:</td>
        <td><%
   1: : this.Model.Date.ToString("dd/MM-yyyy") 
%></td>
    </tr>
    <tr>
        <td>Content:</td>
        <td><%
   1: : this.Model.Content 
%></td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td><asp:Button ID="btnDeleteNewsItem" runat="server" OnClick="DeleteNewsItem" Text="Delete news item" /></td>
    </tr>
</table>

The markup defines a table presenting the news item about to be deleted, and includes the button in the bottom row. The button has an “OnClick” event called “DeleteNewsItem” presented in the last code block.

When a user clicks the button, the UserControls raises the event “NewsItemDeleting”, if anyone listens to it. If our tests are valid, our presenter should listen to it and we have a loose coupling of logic and interaction (Disregarding the attribute, I know!).

Outro

We’ve created a simple interaction with a XML file that holds our news items. So far we can list and delete news items, but the code also contains the ability to create and edit news items. Feel free to look in the code for examples.

Get the code here.