VSM Cover Story

Partition Web Apps Intelligently

Microsoft's Model-View-Controller framework makes it possible to implement "a separation of concerns" in ASP.NET -- something that was difficult, if not impossible, previously. Learn how to take advantage of MVC to design better Web apps that separate their architectural elements -- the UI, business logic, and code -- properly, as well as how to review what you create with unit testing.

TECHNOLOGY TOOLBOX: C#, ASP.NET, Other: MVC Framework

The new ASP.NET Model-View-Controller (MVC) framework from Microsoft offers many new features for Web developers, but two features stand out above the others. The first is that the MVC now facilitates "separation of concerns." The second is that the MVC framework, unlike traditional Web forms, enables the developer to

perform unit tests of the presentation layer much more easily. I'll show you how to take advantage of both these features, first walking you through how to build an MVC application from scratch that features a true separation of concerns, and then show you how to perform unit tests against that same application.

In any object-oriented application, separation of concerns is one of the most important aspects of a clean design. One of the fundamental object-oriented concepts is that a class should have a single job. The purpose of a class becomes unclear when it contains too many members or methods with different responsibilities. The "Extract class" refactoring (Fowler, 1999) is one of the most fundamental refactorings that currently exist to address this situation. The Extract class seeks to break apart the original bloated classes into multiple classes, each with its own distinct purpose.

Traditional ASP.NET Web forms applications have suffered from this age-old problem of a lack of clean separation of concerns because the code behind class often becomes a dumping ground for both business logic and presentation logic. The business logic might consist of getting the appropriate business objects from the data layer, executing business rules for validation, or business processing on those objects. The presentation logic might consist of populating form controls, handling events for form controls, redirecting users to other Web pages, and so on. All of this code can cause the code behind classes to become bloated, with no clear job.

The MVC framework solves the separation of concerns problems of traditional Web forms by partitioning responsibilities between Models, Views, and Controllers (see Figure 1). Note that the Model is unaware of both the Controller and the View. The primary job of the Model is to contain the state that's passed to the presentation layer. The View knows about the Model, but is blissfully unaware of the Controller. The sole purpose of the View is to contain presentation logic. Finally, the responsibility of the Controller is to interact with the data layer and provide the appropriate Models to the appropriate Views. The Controller is the key interaction point in the MVC framework.

Creating the App
Before drilling down into the specifics of the MVC implementation, let's take a step back and consider the details of a sample application that shows off this feature. The app is the Contact module of a personal-information manager such as Outlook. It needs two screens. The first screen shows the list of all my contacts (see Figure 2). It lets you delete any contact, as well as provide links to edit any contact and add new contacts. If the user selects a contact or clicks on the link to add a new contact, the application sends the user to the second screen, where the user can add or edit the contact details (see Figure 3).

The MVC framework utilizes a rich routing engine provided by the System.Web.Routing assembly. The routing engine enables the ability to use URLs that don't map to a physical file. In the context of MVC, the most common pattern for this is "{controller}/{action}/{id}". For example, an MVC page that invokes the ContactController's Create() action method for a given ID might look like this:

http://somedomain/Contact/Create/21

The routing framework is extremely flexible, and the route mappings are set up in the Global.asax file. A full discussion of the routing framework is beyond the scope of this article, but it's important to note that this basic pattern is used throughout this article.

To implement a Controller class, you must inherit from System.Web.Mvc.Controller. Any public method in your Controller that returns a derivative of ActionResult is a valid Controller Action method. Based on the first screen defined, you need Controller actions to display a list of contacts, delete a contact, and send users to the contact details screen to add or modify a contact.

A Controller action to save the contact is also required for the contact details screen. This code provides the ContactController class definition:

public class ContactController : Controller
{
    public ViewResult Index();
    public ViewResult Create(int? id);
    public RedirectToRouteResult Delete(int id);
    public ActionResult Save(Contact contact);
}

The ContactController interacts with the data layer to send and receive the appropriate objects to the data repository. The data layer contains a class called ContactManager that implements an interface called IContactManager:

public interface IContactManager
{
    IEnumerable<GetContactListResult> GetContactList();
    IEnumerable<State> GetStates();
    Contact GetContact(int contactID);
    void SaveContact(Contact contact);
    void DeleteContact(int contactID);
}

A detailed discussion of the data layer is beyond the scope of this article, but it's important to note that the presentation layer works on the IContactManager abstraction of the data layer. Although the data layer is using LINQ to SQL, the presentation layer is blissfully unaware of this fact and requires no reference to the System.Data.Linq assembly. In fact, the LINQ data context class is internal to the assembly, and the presentation layer can't access it.

The next step is to display the list of contacts when the user navigates to the main contacts screen. The first action method to accomplish this looks like this:

public ViewResult Index()
{
    IEnumerable<GetContactResult> contactList = 
    contactManager.GetContactList();
    ViewData.Model = contactList;
    return View("Index");
}

This method is quite simple, but there are some important things happening. First, this code is getting the list of contacts from the data layer. Next, it sets that collection as the Model of the Controller's ViewData. This ViewData is passed to the View. Finally, the returned View is called "Index." The MVC framework searches for a physical file called Index.aspx or Index.ascx in the folder that corresponds to the Controller name or the Shared folder. If you omit the Index name parameter to the View method, it looks automatically for a View that has the same name as the current action method name (Index, in this case). However, you should avoid doing this because you want your code to be as explicit and testable as possible.

Now you want to implement the view. Begin by inheriting from ViewPage:

public partial class Index : 
    ViewPage<IEnumerable<Contact>>

Note that the Index View page specifies a generic type constraint. This lets you access the Model in a strongly typed manner (see the complete implementation of the Index View page in Listing 1). The first aspect that often throws traditional ASP.NET developers is that there are no server controls in the markup. This is an intentional part of the MVC framework. In the MVC framework, there are no ViewState or runat="server" attributes. In traditional ASP.NET applications, the server controls provide abstractions over HTML elements. For example, an ASP.NET Label server control renders itself as an HTML <span> tag when sent to the browser. Alternately, the MVC framework gives the developer complete control over the rendered HTML with no bloated ViewState.

A link must be available for the user to click on, so the user can go to the details page to create or edit a contact. Specifically, you must invoke the ContactController's Create() method. You can accomplish this with the ActionLink method of the HtmlHelper:

<%=Html.ActionLink<
   ContactsController>(c=> 
   c.Create(contact.
   ContactID), "Edit")%>

Using a C# 3.0 lambda expression lets you specify the strongly typed method for invoking the Create. Similarly, you can use a Delete link in the same fashion:

<%=Html.ActionLink<ContactsController>(c=> 
    c.Delete(contact.ContactID), "Delete") %>

Once the user selects a specific contact, they must be sent to the contact details screen so that modifications can be made. There are two pieces of data that the screen requires. First, the appropriate contact object needs to be available. Second, the screen must provide a drop-down list of U.S. states that the user can pick from. How do both pieces of information get sent to the View as a single Model? You accomplish this by creating a simple wrapper class called ContactViewData that acts as your Model. It's a common misconception to think of the business objects as the Model in the ASP.NET MVC framework. This is sometimes true, but it's not always accurate. The Model is actually whatever abstraction is needed for the presentation layer to accomplish its objective properly. This is quite often more than business objects. This code describes the complete implementation of the ContactViewData class:

public class ContactViewData
{
    public Contact Contact { get; set; }
    public IEnumerable<State> StateList { get; set; }

    public ContactViewData(Contact contact,
    IEnumerable<State> stateList)
    {
        this.Contact = contact;
        this.StateList = stateList;
    }
}

Using the Create action method isn't complicated if you take advantage of the ContactViewData:

[AcceptVerbs(HttpVerbs.Get)]
   public ViewResult Create(int? id)
{
    if (id.HasValue)
    {
        Contact contact = 
            contactManager.GetContact(id.Value);
        IEnumerable<State> stateList = 
            contactManager.GetStates();
        this.ViewData.Model = 
            new ContactViewData(contact, stateList);
        return View("Create");
    }
    else
    {
        return View("Create", 
            new ContactViewData(new Contact(), 
            contactManager.GetStates()));
    }
}

Again, the markup in the View is simple and straightforward. Additionally, there's no code required in the code behind. (See Listing 2, for the complete code for the Create.aspx page.)

One thing of note in Listing 2 is the ContactController's Save action method. There are various ways to save form data in the MVC framework. One of the most elegant methods is to utilize Model Binders. Using Model Binders, which implement the System.Web.Mvc.IModelBinder interface, means the act of creating an object from form parameters can be encapsulated in the binders and abstracted away from the Controller actions methods. This allows the Controller action methods to save objects strongly typed for that method. That is, the resulting object from the binder will be passed in to the appropriate Controller method for saving. You can find the complete code for the ContactBinder in the code download for this article).

In this scenario, the first job of the Save method is to validate the object by invoking the validation rules defined in the business layer. If the object is valid, you send it to the data layer for persistence and then redirect the user back to the main screen. Otherwise, you need to show the user the form again, so they can fix the invalid data.

Best practices dictate that validation of business objects should occur in the business layer. Too often, developers implement the validation only in the presentation layer, or worse, duplicate the validation of their business objects in the presentation layer. A great framework to validate business objects declaratively is the Microsoft Enterprise Library Validation Application Block (VAB). The VAB allows you to validate your business objects on the objects themselves. Another great aspect of MVC is that it provides a flexible framework to hook the UI validations into already existing object validations. The VAB is simply one of the many frameworks that can be used to integrate with MVC validation; validating with the VAB and redisplaying the view with all appropriate messages is straightforward (see Listing 3).

Implement Unit Testing
One of the most problematic areas of traditional ASP.NET Web forms is their lack of "testability." Among these issues is a lack of HttpContext when a Web server isn't present. Fortunately, the MVC framework provides an HttpContextBase class that lends itself well to mocking in the context of unit-testing frameworks. Additionally, quality unit tests ensure that only small units of logic are tested in each test. However, the lack of separation of concerns in traditional ASP.NET Web forms makes this quite difficult. The MVC architecture is based on a pluggable model, so it's easy to utilize Dependency Injection to facilitate more granular unit tests.

Proper Test-Driven Development (TDD) principles state that unit tests should be written before actual application code. I've violated that principle in this article solely for the purpose of introducing the MVC framework. The beauty of MVC is that it allows unit tests to be written first in accordance with true TDD best practices.

This article uses MSTest for unit testing, but you could use any unit-testing framework, including NUnit, xUnit, or MBUnit. I highly recommend you select the unit-testing framework that you and your development team are most comfortable with.

This article also uses Moq for a mocking framework, but again, you could use any mocking framework, including Rhyno Mocks or TypeMock. MVC puts no constraints on the personal preference of a developer with respect to using their preferred tools.

The first unit test you create is for the main screen. Specifically, the unit test must verify the logic in the ContractController's Index method. The job of the Index method is to retrieve the collection from the data layer and then pass that collection to the Index view. However, it's important that only the code in the Controller is tested and not the code in the data layer. Therefore, the data layer will be "mocked" so that only the code in the Controller is being tested. Here's the complete unit test for the Index method:

[TestMethod]
public void ContactController_Index_Test()
{
    // set up
    var contactManager = 
        new Mock<IContactManager>();
    IEnumerable<GetContactListResult> 
        contactList = TestUtil.
           CreateContactList();
    contactManager.Expect(c => 
        c.GetContactList()).Returns(contactList);
    // execute
    ContactsController controller = new 
        ContactsController(contactManager.Object);
    ViewResult result = controller.Index();
    // verify
    IEnumerable< GetContactListResult > 
        viewDataModel = result.ViewData.Model 
        as IEnumerable<GetContactListResult>;
    Assert.IsNotNull(viewDataModel, 
        "ViewData should not be null.");
    Assert.AreEqual(4, viewDataModel.Count(), 
        "View data must have items.");
    contactManager.VerifyAll();
}

The Controller contains a reference to the IContactManager interface rather than the concrete ContactManager itself, so you can use dependency injection to pass in a mock contact manager.

Taking a slightly more complex example, a unit test is required to test the Save functionality. Specifically, if a valid object is passed into the Save method, the unit test must verify that the data persistence was invoked and the user was redirected back to the main screen. Similarly, a separate unit test must verify that, if an invalid object is passed in, the validation messages are added and the screen is redisplayed. This code shows you how to test for saving a valid contact:

[TestMethod]
public void ContractController_Save_Valid_Test()
{
    // set up
    Contact contact = TestUtil.CreateValidContact();
    var contactManager = new Mock<IContactManager>();
    var mockedHttpContext = 
        MoqMvcMockHelpers.FakeHttpContext();
    ContactsController controller = new 
        ContactsController(contactManager.Object);
    controller.ControllerContext = new 
        ControllerContext(mockedHttpContext, 
        new RouteData(), controller);
    contactManager.Expect(c => 
        c.SaveContact(contact));
    // execute
    var result = controller.Save(contact) 
    as RedirectToRouteResult
    // verify
    Assert.AreEqual("Index", result.Values["action"], 
        "ViewName is not correct.");
    contactManager.VerifyAll();
}

You can find the complete unit-testing implementation for the entire application in the accompanying code download for this article. One nice aspect of the MVC framework's architecture is that it's completely pluggable, with separation of concerns implemented elegantly. It was designed with testability in mind throughout. This in turn lets you to write higher-quality code.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.