Old School MVP for ASP.Net Web Froms

Saturday, November 23, 2013

Introduction

Sometimes you have to work with a legacy site because, let's face it, it works and it's just not worth the effort to rewrite the whole thing via the newer technologies.

But still, if you need to add a web page and you still want to be able to separate the presentation logic from the domain logic, the Model View Presenter Pattern is the way to go.

I am not going to go into much detail about the pattern itself, there is a great article about it up on MSDN, but I would like to show you a great implementation of it that reduces the amount of code you will need to write to get it going.

The Base View

The first thing to do, is to set up the views, which will be interfaces the web page will use to represent the data on the page.

public interface IView
{
     
}

Yeah, not much here, just an empty interface, but this interface will be inherited from all the other views, making them all recognizable.

The Base Presenter

The base presenter class gets the base view injected into it. By doing this, the presenter will have a way to map the model to the web page.

public abstract class Presenter<TView>
{
    public TView View { getset; }
 
    public abstract void OnViewInitialized();
 
    public abstract void OnViewLoaded();
 
}

The Base Page class all Web Pages Will Inherit From

Now that we have the presenter and the view, we can create a base web page class that will inherit from the System.Web.UI.Page class. Using this class as the base, the web page will automatically inject the presenter and the view onto the page (you can also do this for a UserControl base class).

public abstract class MvpBasePage<TPresenterTView> : Page where TPresenter : Presenter<TView>
    {
 
        protected Presenter<TView> Presenter { getset; }
 
        protected MvpBasePage()
        {
            if (!(this is TView))
                throw new InvalidOperationException("Must impliment the generic type TView");
            try
            {
                Presenter = ObjectFactory.GetInstance<TPresenter>();
                Presenter.View = (TView)((object)this);
            }
            catch
            {
                Trace.Write(ObjectFactory.WhatDoIHave());
                throw;
            }
 
            Debug.WriteLine(ObjectFactory.WhatDoIHave());
        }
    }

I am using StructureMap to register the presenter classes, but any Inversion of Controll container should work here as well.

The Default Page Presenter and View

Now that I have all my base classes ready I can then start using them to create web pages that will conform to the Model View Presenter pattern. So for my default page, I am going to impliment a small version of my trusty sample application, which is a project tracker. You can see an almost full version that uses AngularJs on GutHub, but in this case I am just going mock a client list, which is the first page.

So my client list view is just going to have two properties on it: a content title, and a list of clients (Note: you can also have functions and events on the view for more complicated scenarios).

public interface IClientsView : IView
{
    string ContentTitle { getset; }
    List<Client> Clients {  set; }
}

My presenter for the clients just calls a service that is mocking a list of clients.

public class ClientsPresenter : Presenter<IClientsView>
  {
      private readonly IClientsService _clientsService;
 
      public ClientsPresenter(IClientsService clientsService)
      {
          _clientsService = clientsService;
      }
 
      public override void OnViewInitialized()
      {
          View.ContentTitle = "Client List";
          List<Client> clients = _clientsService.GetAll();
          View.Clients = clients;
      }
 
      public override void OnViewLoaded()
      {
      }
  }

Look Ma! Hardly any code behind!

Now with all this in place my Code Behind file only has the responsibility of marshaling data to and from the view, making it very clean.

public partial class _Default : MvpBasePage<ClientsPresenterIClientsView>IClientsView
    {
        private List<Client> _clients;
 
        public string ContentTitle { getset; }
 
        public List<Client> Clients
        {
            set
            {
                clientsRepeater.DataSource = value;
                clientsRepeater.DataBind();
            }
        }
 
        protected void Page_Load(object senderEventArgs e)
        {
            Presenter.OnViewInitialized();
            if (Page.IsCallback == false)
                Presenter.OnViewLoaded();
        }
    }

And lastly, here is the actual web form

<asp:Content runat="server" ID="FeaturedContent" ContentPlaceHolderID="FeaturedContent">
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1><%: ContentTitle %></h1>
            </hgroup>
            <section>
                <asp:Repeater runat="server" ID="clientsRepeater">
                   <HeaderTemplate>
                        <table>
                        <thead>
                            <tr>
                                <th>Client Name</th>
                                <th>Contact Name</th>
                            </tr>
                        </thead>
                        <tbody>
                            
                        
                   </HeaderTemplate>
                    <ItemTemplate>
                        <tr>
                            <td><%#Eval("ClientName"%></td>
                            <td><%#Eval("ContactName"%></td>
                        </tr>
                    </ItemTemplate>
                    <FooterTemplate>
                        </tbody>
                    </table>
                    </FooterTemplate>
                </asp:Repeater>
            </section>
        </div>
    </section>
</asp:Content>

That's pretty much it.

The great thing here is if you need to work in legacy applications, you can start this pattern at any point. I have added new pages with this pattern into legacy application that do not follow any pattern without much effort.

I put all the code up on GutHub, in case you would like to take a look.

 

comments powered by Disqus