Page 1 of 1

Implementing the MVP pattern in ASP.NET

#1 tidyui  Icon User is offline

  • New D.I.C Head
  • member icon

Reputation: 1
  • View blog
  • Posts: 7
  • Joined: 13-May 11

Posted 18 May 2011 - 04:10 AM

MVP - How about unit testing for WebForms

The full source code for this tutorial is available at my github at https://github.com/tidyui/TidyUI.Web.

MVP - the "Model-View-Presenter" pattern, like the MVC pattern, is used to separate business logic from the application and presentation layer. For a quick walkthrough of the MVP pattern, visit the following link:

http://msdn.microsof...y/ff647543.aspx

Now let's move on to how I've implemented this pattern for ASP.NET.

The implementation consists of two base-classes and an item template for Visual Studio that you can drop in you template directory. The template will then show up with the name "Mvp form" when you choose to create a new Web item in your project. If you were to create a new form with the name Default.aspx you would get the following files.

Default.aspx
    Default.aspx.cs
    Default.aspx.designer.cs
    Default.aspx.presenter.cs
    Default.aspx.view.cs


The three first files you'll recognize as the standard .NET files for a webform. The two at the bottom are the additional files needed to conform to the MVP pattern. Now let's try to map these files against the MVP pattern and see which files implements what part.

Default.aspx.cs - The view
Default.aspx.view.cs - The view interface
Default.aspx.presenter.cs - The presenter


The model is your business logic and should not be grouped with the page. Let's now quickly examine the code that the item template generates for the different files and then build a simple example with it.

Default.aspx.cs - The view
public partial class Default : MvpView<DefaultPresenter, IDefault>, IDefault
{
    protected void Page_Load(object sender, EventArgs e) {}
}


As we can see the code-behind now inherits the generic class MvpView instead of the standard System.Web.UI.Page. The generic base-class is supplied with the type of the presenter and the view interface, this allows the base-class to create a new presenter object in it's constructor ready to use by the view.

public abstract class MvpView<TPresenter, TView> : Page where TPresenter : MvpPresenter<TView>
{
    /// <summary>
    /// Gets/sets the current presenter for the view.
    /// </summary>
    public TPresenter Presenter { get ; set ; }

    /// <summary>
    /// Default constructor. Creates a new MVP view.
    /// </summary>
    public MvpView() : base() {
        if (!(this is TView))
            throw new Exception("MvpView must implement the interface provider as generic TView type") ;

        // Create and initialize presenter
        Presenter = Activator.CreateInstance<TPresenter>() ;
        Presenter.View = (TView)((object)this) ;
    }
}


Default.aspx.view.cs - The view interface

public interface IDefault
{
}


As we can see the view interface doesn't contain anything at this point, but we'll get to this shortly when we create our simple example.

Default.aspx.presenter.cs - The presenter

public class DefaultPresenter : MvpPresenter<IDefault>
{
}


The presenter inherits the generic class MvpPresenter to which we provide the same view interface as we provided to the view implementation. This allows for the base-class to contain a typed property for the current view object.

public abstract class MvpPresenter<T>
{
    /// <summary>
    /// Gets/sets the current view.
    /// </summary>
    public T View { get ; set ; }

    /// <summary>
    /// Default constructor. Creates a new presenter without an assigned view.
    /// </summary>
    public MvpPresenter() {}

    /// <summary>
    /// Creates a new presenter for the given view.
    /// </summary>
    /// <param name="view">The view</param>
    public MvpPresenter(T view) {
        View = view ;
    }
}


So, how about that example then?

Sure, first of all we'll add a simple Label to our asp-page, let's call it lblName. No need to show that as you all know how to do it. Second of all let's add a property for the name in our view interface, like this:

public interface IDefault
{
    ///
    /// Gets/sets the current name.
    //
    string Name { get ; set ; }
}


In order for everything to compile, we need to add an implementation for this to our view:

public partial class Default : MvpView<DefaultPresenter, IDefault>, IDefault
{
    protected void Page_Load(object sender, EventArgs e) {}

    #region IDefault Members
    public string Name { 
        get { return lblName.Text ; }
        set { lblName.Text = value ; }
    }
    #endregion
}


Ok, now what? Well now we simply add the business method in our presenter for fetching the name. In a real scenario this method would probably access a model which would load the name for the current user from a database somewhere. But for now, let's just set it to John Doe.

public class DefaultPresenter : MvpPresenter<IDefault>
{
    public void GetName() {
        View.Name = "John Doe" ;
    }
}


And last, but not least, let's call our presenter action from our view implementation:

public partial class Default : MvpView<DefaultPresenter, IDefault>, IDefault
{
    protected void Page_Load(object sender, EventArgs e) {
        if (!IsPostBack)
            Presenter.GetName() ;
    }

    #region IDefault Members
    public string Name { 
        get { return lblName.Text ; }
        set { lblName.Text = value ; }
    }
    #endregion
}


Now why on earth did we write all that code?

Yes indeed, why did we just add two extra files and added some complexity to our application architecture? The simple answer is testability. If you think about it we've just managed to extract all application logic from the code-behind to a separate class with a nice and clean API. We've also made sure that the presenter knows nothing about the class implementing the view interface, making sure we can supply it a pure container for test data.

All of this together adds up to the fact that we can now easily create a test project in .NET and apply unit testing to our application, like so:

public class DefaultTestView : IDefault
{
    public string Name { get ; set ; }
}

[TestClass]
public class DefaultTest 
{
    [TestMethod]
    public GetNameTest() {
        IDefault view = new DefaultTestView() ;
        DefaultPresenter presenter = new DefaultPresenter(view) ;

        presenter.GetName() ;

        Assert.AreEqual("John Doe", view.Name) ;
    }
}


I hope you found this tutorial useful.

Is This A Good Question/Topic? 0
  • +

Page 1 of 1