DISCLAIMER: This was written all the way back in Feb. 2006 when we thought that MVP with ASP.NET was the only real alternative for somewhat testable web user interfaces. I thank you for the interest, but there have been better articles written since, and I'd strongly recommend looking at MonoRail or Ruby on Rails or the new MVC framework for ASP.NET if you're interested in maintainable, testable web applications. Let me make this even clearer, I think the technique in this article is one giant, smelly hack. The only adequate solution is to dump WebForms, or convince yourself that TDD, Testability, and Separation of Concerns is meaningless
A friend of mine was asking me a while back about ways to apply the Model View Presenter (the “Humble Dialog Box”) pattern to ASP.Net development to promote easier unit testing of the user interface layer. Two weeks and a major case of writer’s block later, I finally finished the post. I wrote a blog post last spring describing the usage of MVP (“The Humble Dialog Box”) with WinForms clients, but web development in general and ASP.Net in specific comes with a different set of challenges.
So what are the problems with ASP.Net that MVP Solves?
If you asked me to name the single most important principle in all of software design, regardless of language, platform, or programming paradigm, I’d say “Separation of Concerns” in a heartbeat. For the sake of maintainability, each piece of code should have one and only one distinct responsibility. Even in the initial act of coding I only want to work on one problem at a time. I want to look at the business logic code without seeing database infrastructure code or HTML construction intermingled with everything else (a huge pet peeve of mine).
The traditional approach for building maintainable user interface code is to separate the typical concerns of a user interface with the Model View Controller (MVC) pattern. Unfortunately, ASP.Net is optimized for Rapid Application Development, not for creating maintainable code. A common criticism of the ASP.Net development model (and ASP classic before that) is that it does not enforce or even encourage a clean Model View Controller separation. Many ASP.Net applications seem to be a jumble of data access code, HTML markup, and business logic, but it doesn’t have to be that way. By itself, the “code-behind” model in ASP.Net does not do enough to create a good separation of concerns. Plus it’s difficult to get code-behind code into unit tests.
My preference is to use the Model View Presenter pattern to structure ASP.Net code to create maintainable and testable solutions. I think of MVP as just a flavor of MVC, but it’s worth stating the differences. The central fact of MVP architectures is that the “view” is a very shallow wrapper around the actual screen. Unlike most traditional MVC implementations, the view classes are entirely passive. The Presenter classes are the active classes that are responsible for initiating the view processing.
The big challenge in unit testing ASP.Net code is the tight coupling to the ASP.Net runtime. The web controls are very tightly bound to the underlying HttpContext and the web event pipeline, making the user interface code difficult to work with inside an xUnit test harness. I’ve seen people successfully create fake HttpContext objects in memory, but it really amounts to a hack to test existing legacy ASP.Net code. You can also use tools like NUnitASP or Selenium to test web applications, but those tests are integration tests, not unit tests. The best thing to do in my experience is to isolate as much of the functionality as possible away from the ASP.Net runtime into classes that can be easily tested inside of NUnit, and that’s exactly what the MVP pattern does. Putting this another way, I would suggest that any piece of code that isn’t directly manipulating web controls or parts of the web page should be moved out of the code behind classes and into another class.
A lot of .Net literature describes the “Code-Behind” class as the MVC controller. I think this is dead wrong. The code-behind is View code, period. For best results, the presenter should never have any reference to any type in the System.Web namespace.
Class Structure of MVP
The basic concept is simple. Like the classic Model View Controller pattern, you divide the responsibilities of the web page into separate classes. In specific, we want to isolate as much of the functionality as possible away from the ASP.Net runtimes. The functionality of a screen is divided into three parts:
- View classes: Displays data and relays user input and events to the Presenter.
- Presenter classes: Coordinates communication between the view and the backend service or business layer and is responsible for user interface logic
- Model classes: The data being displayed or worked on in the screen. This is could be either a business domain object(s), a data transfer object, or a lowly DataSet. The Model is also the state of the user session.
A Simple Example
Let’s dive into some code. You’re creating a new web screen in a custom workflow application for people to enter and assign “WorkItem’s” to other people. The screen will have dropdown lists for both the WorkItem category and the assigned user. In no particular order, let’s start with the Model class.
public class WorkItem { private string _category; private string _assignedTo; public string Category { get { return _category; } set { _category = value; } } public string AssignedTo { get { return _assignedTo; } set { _assignedTo = value; } } /* Other stuff */ }
The next step is to create an abstracted interface to represent the View class. Note that we’re not yet working with any System.Web classes, web pages, or web controls. The IWorkItemView interface encapsulates all interaction with the ASP.Net runtime.
// Abstracted interface of the WorkItem View screen public interface IWorkItemView { // Packs the user input into a new WorkItem class WorkItem WorkItem{get;} // Sets the values in the dropdown list for the WorkItem assigned to user string[] AssignmentList {set;} // Sets the values in the dropdown list for the WorkItem categories string[] CategoryList {set;} }
The backend business and workflow system is accessed by a Façade class with an interface called IWorkItemService. The backend might be a call to a web service, a remoted object, or an in-process call. All we’re concerned with in the user interface code is how the presenter interacts with the interface of the service.
public interface IWorkItemService { void ProcessWorkItem(WorkItem entry); string[] GetAssignmentListForCategory(string category); string[] GetCategoriesForRoles(string[] roles); }
The lynchpin of the MVP pattern is the Presenter class, WorkItemPresenter. In this particular user story there is a requirement that the categories displayed in the dropdown on the screen are limited by the role membership of the logged in user. There is another requirement that the values in the dropdown for the assigned to user are dependent upon the selected category. The logic that determines the topic and category list is completely isolated from the web page machinery for easier unit testing and maintainability.
public class WorkItemPresenter { private readonly IWorkItemView _view; private readonly IWorkItemService _service; // Use "Dependency Injection" to attach the dependencies for the view // and service. public WorkItemPresenter(IWorkItemView view, IWorkItemService service) { _view = view; _service = service; } public void Initialize() { string[] categoryList = this.determineCategoryListFromUserRoles(); _view.CategoryList = categoryList; } public void Submit() { WorkItem item = _view.WorkItem; _service.ProcessWorkItem(item); } public void ChangeCategory(string categoryName) { string[] assignmentList = _service.GetAssignmentListForCategory(categoryName); _view.AssignmentList = assignmentList; } public string[] determineCategoryListFromUserRoles() { // Examine the roles on the IPrincipal object and query IWorkItemService for // the possible categories return new string[0]; }
}
The key things to note here are that the WorkItemPresenter class only depends on the abstracted interfaces for IWorkItemView and IWorkItemService. The WorkItemPresenter class can now be unit tested with mock objects inside of the NUnit. WorkItemPresenter is just a “Plain Old CLR Object” that can run and be tested completely outside of the ASP.Net process. This is strictly personal preference, but I like to build and unit test the Presenter class(es) before I even start to create the ASPX or ASCX’s.
[TestFixture] public class WorkItemPresenterTester { private DynamicMock _serviceMock; private DynamicMock _viewMock; private WorkItemPresenter _presenter; [SetUp] public void SetUp() { _serviceMock = new DynamicMock(typeof(IWorkItemService)); _viewMock = new DynamicMock(typeof(IWorkItemView)); _presenter = new WorkItemPresenter((IWorkItemView)_viewMock.MockInstance, (IWorkItemService)_serviceMock.MockInstance); }
}
Lastly, we’ve got to actually create the web page itself. I’m using a UserControl as an example, but it could easily be a web page instead. The WorkItemView UserControl implements the IWorkItemView interface. It has a reference to an instance of the WorkItemPresenter class with which it communicates.
public class WorkEntryView : UserControl, IWorkItemView { private WorkItemPresenter _presenter; protected DropDownList assignmentDropdown; protected DropDownList categoryDropdown; // The UserControl has a reference to the presenter class public void AttachPresenter(WorkItemPresenter presenter) { _presenter = presenter; } private void Page_Load(object sender, EventArgs e) { categoryDropdown.SelectedIndexChanged += new EventHandler(categoryDropdown_SelectedIndexChanged); } #region Web Form Designer generated code public WorkItem WorkItem {…} /// <summary> /// Fill the dropdown list for the possible assignments /// </summary> public string[] AssignmentList { set { assignmentDropdown.Items.Clear(); foreach (string assignment in value) { assignmentDropdown.Items.Add(assignment); } } } public string[] CategoryList{…} // The UserControl immediately relays the user selecting a new Category to the // presenter object. The presenter will decide what to do next private void categoryDropdown_SelectedIndexChanged(object sender, EventArgs e) { string category = categoryDropdown.SelectedValue; _presenter.ChangeCategory(category); }
}
In this case I have an ASPX page that contains a WorkItemView control. In the Page_Load event of the ASPX page I create a new instance of the WorkItemPresenter, attach it to the user control, and call Initialize() on the presenter class to start the display.
public class WorkItemPage : System.Web.UI.Page { protected WorkEntryView workEntryControl; private void Page_Load(object sender, System.EventArgs e) { WorkItemPresenter presenter = new WorkItemPresenter(workEntryControl); workEntryControl.AttachPresenter(presenter); presenter.Initialize(); }
}
State Management
A big challenge in any complex web application is session state. Think of a typical online store like Amazon. On one hand the logic involved in maintaining the session state is important and certainly deserving of isolated unit tests. If I was coding for Amazon, I would certainly want to be writing unit tests around maintaining the state of the “shopping cart” when users select and remove items. The state of the shopping cart will affect the way the page is displayed and the navigation logic after the page. That’s exactly the kind of logic that is hard to test if you don’t loosely couple the logic from the HttpContext.
On the other hand, a lot of web screens are only reachable from a series of user actions on previous pages. The checkout screen can only be reached after working through previous screens. My first TDD project was an ASP.Net project that had some web pages like this. We were using NUnitASP to write automated tests. I didn’t know much about mock objects or MVP (or MVC), so the only way to test my pages and all of their logic was to write tests that progressed through multiple screens just to get to the point where I really wanted to test. Those tests were laborious to write and very brittle. Smaller tests are always easier to write and read. What I’ve learned since then is to either create an abstraction around the state management that the presenter classes depend on, or have another class push the state into the presenter. I touched on this strategy in an earlier post on mock objects.
At some point you do have to test the actual web screens, and it’s a great thing to have automated testing on the user interfaces. It’s just a theory, but I’d think seriously about creating intermediate integration tests that use stubs for all the backend and state management to isolate the web views. You can use a dynamic service locator like StructureMap to substitute stubs for the services with configuration, or create separate ASPX pages to host the UserControl’s and presenter classes, but use the stub objects instead. You could then write much simpler Selenium tests to verify granular parts of the user interface in isolation. You’ve got to maintain loose coupling between the user interface code and the backend to pull it off though. As usual, testability and good design practices are very closely aligned.
User Input Validation
I’ve had a couple of conversations over the last year on the proper place for the user input validation. I still think you have to take advantage of the built in validation controls in the ASP.Net views if you can do it in a declarative way. Any kind of validation more complicated than “this field is required” or numeric validation should probably be put into some sort of presenter class or the model classes. By and large, you want any kind of code that spawns bugs under automated unit testing. Input validation definitely meets that criterion.
Something I like to do is use what I think of as “Embedded Controllers.” You need to be cautious in using the MVP pattern to keep the presenter classes from getting too big with too many responsibilities and preventing the bidirectional view-presenter communication from being way too chatty. I try to alleviate the presenter “blob” anti-pattern by moving validation logic into other controller classes that are used from within the views. I also like to move functionality like filtering, paging, and sorting to these secondary controller classes. It’s just a way of keeping the size of the classes down and maintaining class cohesion.
Easier NFit/FitNesse Testing
We’ve had some success with writing our FitNesse tests directly against the Presenter classes. We have been making the Fixture class for the screen implement the interface for the view and passing itself into an instance of the Presenter, then letting an ActionFixture drive the screen. It’s a lot simpler than trying to drive the tests against the screens themselves. Our sister office is using FitNesse to drive Selenium tests to great effect, but they’re doing it because they don’t have a suitable service layer to test business logic through. These Fixtures look something like this:
using System;using fit; namespace WebSample{ public class DataEntryFixture : Fixture, IWorkItemView { private string[] _assignments; private string[] _categories; private WorkItemPresenter _presenter; public DataEntryFixture() { _presenter = new WorkItemPresenter(this, new WorkItemService()); } public WorkItem WorkItem { get { throw new NotImplementedException(); } } public string[] AssignmentList { set { _assignments = value; } } public string[] CategoryList { set { _categories = value; } } public string SelectedCategory { set { _presenter.ChangeCategory(value); } } }
}
Related Concepts
Posted
02-01-2006 10:14 PM
by
Jeremy D. Miller