Model View Presenter (MVP) is a design pattern used for web as well as windows application. It is especially used where the application has some user interface for interaction like winform for windows app and webform for web app. In this tutorial, I will show you how to implement MVP in a web app as well as a windows app. Another important aspect of MVP patterns is to do unit tests of your whole interface as well as business layer. This really is very useful when the application size becomes really very large. The main objective of MVP is Separation of Concern or Loosely Couple the entire system, so that we can test each part of the application.
For example, I will take Northwind database. I generally use LINQ for accessing database so I have created a dbml for the whole bunch of Northwind database.
Here we can represent data layer as our model. Frankly speaking, model represents the datalayer and datalayer could be your traditional classes to access database or some services to access database. Here we will use LINQ classes for our datalayer.
The web interface is very simple in order to make the illustration purpose easy. Let's talk about our web interface. It will load with all categories and for category selection; it will show the list of products under the selected category. It should have one dropdownlist to hold all the categories. When user selects the category from category listbox, a gridview will be shown with the all products under the category. It's obvious that we have two events; one is pageload on which all the categories will be shown in the category list; another is selected index change of the dropdownlistbox on which the products will be populated for the selected category in the products gridview. Also there should be some list/collection classes to hold the list of category/product.
So for our view, we will create the interface and one delegate. The code is shown below.
public delegate void CategoryChangeEventHandler(object source, CategoryEventArgs e);
public interface IProductListing
{
IEnumerable<Category> Categories { get; set; }
IEnumerable<Product> Products { get; set; }
event EventHandler ListingCategory;
event CategoryChangeEventHandler ChangeCategory;
}
The code is very simple to understand. We declare two enumerable interfaces to hold the list of categories/products and two events; one is listing of category and another is category changing. In ChangeCategory event, we declare a custom eventargs class to hold the selected category id. The code for CategoryEventArgs is shown below.
public class CategoryEventArgs : EventArgs
{
private Int16 _CategoryID;
public CategoryEventArgs(Int16 CategoryID)
{
this._CategoryID = CategoryID;
}
public Int16 CategoryID
{
get
{
return this._CategoryID;
}
set
{
this._CategoryID = value;
}
}
}
Let's show the code for the presenter class. Presenter class is responsible for doing something when view raises event and presenter will respond to the event. To get the view instance, we pass the view through the presenter's constructor. This method is called constructor injection – a Dependency Injection method. Then we will subscribe the view events with methods declared in the presenter. The code is shown below.
public class ProductListingPresenter
{
IProductListing _view;
public ProductListingPresenter(IProductListing view)
{
this._view = view;
this._view.ListingCategory += this.OnCategoryListing;
this._view.ChangeCategory += this.OnCategoryChanged;
}
private void OnCategoryListing(object source, EventArgs e)
{
NorthwindDataContext oDataContext = new NorthwindDataContext();
this._view.Categories = from category in oDataContext.Categories
select category;
}
private void OnCategoryChanged(object source, CategoryEventArgs e)
{
NorthwindDataContext oDataContext = new NorthwindDataContext();
this._view.Products = from product in oDataContext.Products
where product.CategoryID == e.CategoryID
select product;
}
}
Here you can see the view is passed through the presenter using constructor injection. Now we subscribe the ListingCategory event of the view with presenter's OnCategoryListing method. Also we subscribe the ChangeCategory event of the view with thw presenter's OnCategoryChanged method.
Within these methods we call the LINQ classes to populate the data and set the data through view's property. That's it and we can test our whole application.
In the beginning, I said that MVP pattern really makes the unit testing easier. In context to this example, we don't need to write the aspx pages to test the application. We will write a mock class to test the application functionalities and later on we put the code in the aspx page.
Here is the code for mock class.
public class MockProductListing : IProductListing
{
IEnumerable<Category> _categories;
IEnumerable<Product> _products;
public void TestPageLoad()
{
EventHandler handler = this.ListingCategory;
if (handler != null)
{
handler(this, new EventArgs());
}
}
public void TestProductListingByCategory()
{
CategoryChangeEventHandler handler = this.ChangeCategory;
if (handler != null)
{
CategoryEventArgs oArgs = new CategoryEventArgs(Convert.ToInt16(1));
handler(this, oArgs);
}
}
#region IProductListing Members
public IEnumerable<Category> Categories
{
get { return this._categories; }
set { this._categories = value; }
}
public IEnumerable<Product> Products
{
get { return this._products; }
set { this._products = value; }
}
public event EventHandler ListingCategory;
public event CategoryChangeEventHandler ChangeCategory;
#endregion
}
In the mock class, we need to implement the view interface. We will test only two events; one is pageload; another is category dropdownlist selected index changed. You can see, I have written two test methods to test these two events.
Finally we will write the test mothods.
[TestMethod]
public void PageLoad()
{
List<Category> categories = new List<Category>();
MockProductListing _view = new MockProductListing();
ProductListingPresenter _presenter = new ProductListingPresenter(_view);
_view.TestPageLoad();
foreach (Category cat in _view.Categories)
{
categories.Add(cat);
}
Assert.AreEqual(8, categories.Count);
}
[TestMethod]
public void TestProductsByCategory()
{
List<Product> products = new List<Product>();
MockProductListing _view = new MockProductListing();
ProductListingPresenter _presenter = new ProductListingPresenter(_view);
_view.TestProductListingByCategory();
foreach (Product prod in _view.Products)
{
products.Add(prod);
}
Assert.AreEqual(12, products.Count);
}
Run the test methods and you will find the expected results.
So now, you can realize how MVP helps to test our application code. In original representation, we just need to copy the mock class's code to the aspx page.