clausn.dk

in my mind

World War II, told with Facebook terminology

clock August 19, 2010 08:17 by author cln

This is an unusual post, but I simply have to share this story about world war II!

Great fun!

WorldWar2ToldWithFacebookTerminology



WebformsMvp – Part 2

clock July 28, 2010 10:06 by author cln

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.



WebformsMVP – Part 1

clock July 28, 2010 09:24 by author cln

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



Palm Springs og LA

clock July 25, 2009 06:20 by author cln

Idag forlod vi Palm Springs efter at have nydt en aften ved et hyggeligt marked og afslapning ved pool’en:

IMG_8430

IMG_8432

Da mørket faldt på blev det for alvor hyggeligt:

IMG_8457

Et hyggeligt marked, men med 41 grader var det næsten for varmt at gå rundt. Det var dejligt de sprøjtede vand ud fra butikkerne, således man lige kunne komme af med lidt varme. Da vi kom tilbage til hotellet kunne vi tage en dukkert i poolen for at blive kølet lidt af:

IMG_8460

 

Det var dagen igår, men idag gik turen som beskrevet til Los Angeles, hvor vi som den første dag faktisk ikke har taget nogle billeder! Da hotellerne først kan garantere at værelset er frit typisk fra kl. 15, skulle vi finde noget at få dagen til at gå med og hvad er mere oplagt en at shoppe. Julia havde fundet et Mall i nærheden af hvor vi boede, som skulle være meget berømt. Der var 130 butikker som f.eks. Hugo Boss, Calvin Klein, Prada, Fossil, Nike, Levis, Diesel og mange flere – det bedste ved det var bare at det var outlet butikker, så priserne var meget bedre :)

Dagen gik altså med at slæbe og prøve det ene og det andet. Vi fik faktisk gået forbi alle 130 butikker, men undgik heldigvis at besøge dem alle. Der blev købt nederedele, skjorter, sweatere og undertøj, så nu skulle kufferten gerne være fyldt.. Lad os håbe det kan være der :)



Heart attack

clock July 24, 2009 07:42 by author cln

At forlade Grand Canyon var lidt hårdere end vi troede, så vi tog en tur langs kanten igen, inden vi skulle køre til Flagstaff:

IMG_8337

Vi gik en tur rundt og besøgte et mindre Mall, hvor vi kunne kigge på butikker.
Under aftensmaden begyndte det at regne. På turen hjem kunne vi fange et billede:

IMG_8377

Efter en afslapningsaften kunne vi køre vestpå mod Palm Springs, men først skulle vi forbi Pheonix:

IMG_8395

Så blev jeg “indlagt”:

IMG_8398

Så fik jeg en “Triple Bypass Burger”:

IMG_8400

Efter maden fik jeg et hurtigt tjekup:

IMG_8404

Herefter kunne vi fortsætte mod Palm Springs :)



Grand Canyon

clock July 24, 2009 07:23 by author cln

Så er Grand Canyon allerede overstået, hvor vi har fået set en masse. Vi havde 2 overnatninger ved kanten, hvor vi brugte en dag på at vandre ned i den og gå langs kanten. Derudover så vi en IMAX film, som viste en flot film med lidt historie og en masse flotte billeder af kløften.

Her starter vores tur ned i kløften:

IMG_8060

Her går turen nedad:

IMG_8067

Vi skulle gå ned ad stien, hvor der først ville være et sted de kalder “Uhh Ahh”. Der var ingen skilt der markerede stedet, men som brochuren sagde, så ville man vide når man var der. Det havde de ret i:

IMG_8087

Dejlig sti rundt:

IMG_8099

Det næste stoppested var Cedar Ridge:

IMG_8126

Fantastisk udsigt:

Cedar Ridge - Cropped

Så gik turen op igen, som forventet var noget hårdere end turen ned:

IMG_8205

Det er et stort hul i jorden:

IMG_8222

På ovenstående billede kan man se platoet vi gik ned på. En fantastisk tur med en rigtig flot udsigt.
Da vi kom op trak det op til tordenvejr:

IMG_8233

Vi fik en masse bulder og brag som var rigtig flotte.

Aftenen gik med at se solnedgangen over Grand Canyon:

IMG_8240

 

En fantastisk oplevelse at se Grand Canyon!



Monument Valley

clock July 20, 2009 05:39 by author cln

Så nåede vi Kayente og dermed punkt K på vores kort.

Dagen er gået med at køre hertil fra Moab, som tog lidt over formiddagen med frokost og en optankning. Vi tog nemlig et stop ved “Four Corners”, hvor staterne Arizona, Utah, Colorado og New Mexico mødes i et enkelt punkt:

IMG_7827

Nogen i min familie vil kunne kende dette punkt :)

Derefter var vi klar til at se Monument valley og det var bestemt en oplevelse værd.
Monument Valley er en dal fyldt med store klumper bjerge, som lettest kan beskrives ved at vise dem:

IMG_7850

IMG_7875

IMG_7926 (Denne bliver kaldt tommelfingeren)

Her er et eksempel på, hvordan Navajo indianerne boede:

IMG_7855

Der var også tid til at lave reklamefotos:

IMG_7930

Vi så også en tornado:

IMG_7861

Fantastisk natur de har og man bliver imponeret over hvor stort det er!



Velkommen til EU

clock July 20, 2009 05:06 by author cln

En ting vi finder rimelig morsomt her i USA er faktisk mobilnettet.

Hvorvidt man kan give USA’s mobilnet eller Telmore, som vi begge har, er lidt svært at vide, men en ting er sikkert – for mobiludbyderne er vi åbenbart i EU.

Vi har begge modtaget en SMS fra kundeservice, som pænt byder os velkommen til EU. “Pris/min. inkl. moms…” lyder beskeden, hvilket vi jo synes er en smule morsomt, da vi befinder os i USA.

Det sjoveste sted jeg modtog en, var i Death Valley, som må siges at være et af de ødeste steder i USA, hvilket gør det lidt underligt at vi modtager disse.

Hvis bare prisen var, hvad de skrev i den sms…



2 rejser øst

clock July 19, 2009 07:01 by author cln

Idag har været en lang dag, for vi skulle stå tidlig op og se solopgangen i Bryce Canyon. Det var rigtig flot og vi nåede det lige et par minutter før solen kom op over bjerget og vi var endda tidlig på den. Damen i receptionen måtte have brugt den samme tid hele ugen…
Flot var det:

IMG_7746

Herefter kørte vi til Moab (Punkt J), hvor vi skulle se nogle flotte buer udformet i sandstenene oppe i bjergene:

IMG_7803

Her sidder Julia i den midterste af de 3 buger ovenfor:

IMG_7789

Der var dog store problemer med at gå rundt der, da der ingen skygge var og bilen sagde 108F = 42,2C!



Zion National Park og Bryce Canyon

clock July 18, 2009 05:40 by author cln

Efter Vegas kørte vi til Zion national Park, som var en rigtig flot park med flotte bjergtoppe:

IMG_7504

Der gik en flot lille flod igennem:

IMG_7531

IMG_7560

Kløften var utrolig smuk og vi fik også gået lidt op igennem dalen. Utrolig flot natur!

Idag kørte vi til Bryce Canyon, som ligeledes er en utrolig flot bjergkæde:

IMG_7627

Denne bjergkæde er kendetegnet ved at have formationer som ovenstående billede.
Vi tog en shuttle bus til et af stoppestederne hvor vi kunne vandre ned i dalen:

IMG_7644

Her kigger vi op ad den tur vi gik ned:

IMG_7655

Derudover kunne vi stå et sted, hvor vi havde udsigt over hele dalen:

IMG_7717 Stitch-2

Bryce Canyon var bestemt besøget værd og det er virkelig nogle fascinerende klippeformationer:

IMG_7725

Her bor vi, lige bag den sorte bil i midten af billeder:

IMG_7740



About the author

Claus Nielsen

My name is Claus Nielsen and I am a .NET developer located in Århus, Denmark.

Tag cloud

Month List

Sign in