ASP.NET Model View Controller Part 2

This article was originally published in VSJ, which is now part of Developer Fusion.
In the first article in this series in May, I introduced the concepts behind the ASP.NET implementation of Model View Controller (MVC). I talked about how a request will be routed to a controller; how the appropriate action in the controller is called; and how this action causes a view to be rendered. In this article I’m going to talk more about routing and testing.

Routing

In the first article we saw that an application builds a routes table in Global.asax. It initially looks like this:
routes.Add(new Route("{controller}/
	{blogtitle}/{action}/{id}",
	new MvcRouteHandler())
	{
		Defaults = new
			RouteValueDictionary(
			new { action = "Index",
			id = "" }),
	}
);
routes.Add(new Route("Default.aspx",
	new MvcRouteHandler())
{
	Defaults = new RouteValueDictionary(
		new { controller = "Home",
		action = "Index", id = "" }),
});
The first route says: for anything that matches {controller}/{blogtitle}/{action}/{id} (where the {} are placeholders) map the {controller} section to an MVC Controller class and {action} to a public method on the class, passing {blogtitle} and {id} as a parameter to that method, and the default part says that if {action} and {id} are missing, use “Index” and “” respectively for these values. The second Route says that if anybody requests Default.aspx route them to /Home/Index, as given by the default values.

Routes are checked in the order they are specified in the RouteTable. This means that if we want specific controllers handled in specific ways then we need to add those entries to the table first. For example, if we want a URL like http://localhost/archive/2008/04/21 then we would need to add route like this to the routes table.

routes.Add(new Route(
	"archive/{blogTitle}/{year}/
	{month}/{day}/{entry}",
	new MvcRouteHandler())
{
	Defaults= new RouteValueDictionary(
		new {controller="archive",
		action="find"}),
});
Also notice that this route has a number of parameters, the year, month, day and entry. This shows that the route entry is not limited to simply controller/action/id and as as flexible as we need it to be.

Routes can also include constraints. We add contstraints when we create a route instance. For example, we could specify that only specific HTTP methods must be used for certain routes, this is especially useful if we have a REST-ian architecture. We can also limit parts of the route to only contain specific values, for example that the {year} field above must contain exactly 4 digits.

We do that by adding a Constraints member to the route, so for the above route we can add the following:

routes.Add(new Route(
	"archive/{blogTitle}/{year}/
	{month}/{day}/{entry}",
	new MvcRouteHandler())
{
	Constraints = new
		RouteValueDictionary(new
	{
		year = @"\d{4}", httpMethod = "GET"
	}),
	Defaults = new
		RouteValueDictionary( new
	{
		controller="archive",
		action="find"}),
});
The routing system also lets us create outgoing URLs. For example in the previous article I showed the use of the HTML ActionLink helper, the code looked like this:
<%# Html.ActionLink("Edit Entry",
	"Edit", "Blog",
	new { id=Eval("Id")})%>
<%# Html.ActionLink("Delete Entry",
	"Delete", "Blog",
	new { id = Eval("Id") })%>
This code picks up the routes defined in the application and uses that data to construct URLs on the page that look like this:
<a href="/Blog/BlogoftheBlog/Edit/13">
	Edit Entry</a>
<a href="/Blog/BlogoftheBlog/Delete/13">
	Delete Entry</a>
ActionLink generates the anchor tag as shown above. If we want the actual URL without the surrounding HTML markup then there is a helper method called ActionLink on the URL class. Using this:
Url.Action("Edit", "Blog", new {id=1})
…would give a url of:
/Blog/BlogoftheBlog/Edit/1
We can use this anywhere we need a URL, for example in code or in a form tag within a page.

There are going to be occasions when we need to redirect from the current action to a particular action. For example, if we are managing security within a page and the user has not been authenticated we may need to redirect them to a particular page, or if there is an error while processing the current request. We may also redirect when the user has POSTed something using a page and we want to send them to a display page. This latter technique is often used to stop the user re-POSTing a form, and to give the user a URL that they can bookmark. To do this in ASP.NET MVC you can use the RedirectToAction method, like this:

RedirectToAction("Index", "Blog");
This will send an “HTTP 302 found” response with a Location header specifying the redirected URL.

Finally, we can also specify routes using the C# 3.0 lambda syntax. For example the route shown above:

<%# Html.ActionLink("Edit Entry", "Edit",
	"Blog", new { id=Eval("Id")})%>
…can also be expressed as:
<%# Html.ActionLink<BlogController>(b =>
	b.Edit((int)Eval("Id")), "Edit Entry")%>
In this case the first parameter to the ActionLink method is a lambda. This lambda specifies the Edit method as a parameter to the blog. This will create the same URL as the standard ActionLink entry, but now as a method is specified we get type safety of the method parameters and also refactoring support.

Testing

As mentioned in the previous article, one of the primary advantages of using an MVC framework is testing. There are several things we need to test. In a layered application we will want to test the business logic layer and the data access layer. These should be testable completely independently of the application using them, in this case an ASP.NET web application. We often also want to test the web application itself. Testing a web application falls into two distinct parts. Firstly, we can test the output from the application; this involves sending the application a request and examining the response. The response will consist of any HTTP response headers, the response code and any output HTML. This testing takes place on the client and is, essentially, functional testing of the application. To do this we need a client side tool that will load an HTML DOM and check that things are where we expect them to be. So, for example, we would check that the divs are in the correct place, that a table contains the correct number of cells and rows with the correct data in them.

The other testing of web applications takes place on the server. Before the view is rendered the server code will have executed some business rules and stored data in various locations, for example in the session and in the ViewData object. We will need to check that this data is correct. For example, if the user sends a request to find a particular blog entry, we need to check that the blog entry data we expect is correct in the server before it is rendered back to the client.

There are a couple of approaches to testing server side code in this way. One approach is to use an HTTP client to send a request to the server. When the request gets to the server, the server will create all the ASP.NET objects as normal, so we will have access to the Session, Request, Response, etc. Testing code can intercept the call, inject the data needed for the test into these server objects, then call the method we want to test. When the method returns we can check that the server side objects are now in the correct state. This is the approach taken by Java testing frameworks such as Cactus.

Another approach is to ‘mock’ the server side objects in your test code, and then call the method under test with these mock objects. Frameworks such as MBUnit, Rhino Mocks and NMock offer this functionality.

Both approaches have their benefits. With the first we can be sure we are executing in the correct environment, however there is an overhead to this. The second is more lightweight as we do not need a web server, however, setting up the mock objects can be difficult.

Testing the Blog

For the testing we are going to do here, we will use Rhino Mocks. The primary idea behind mock testing is that we create only the objects we need for the test. This means that when testing a web application we shouldn’t need to create instances of objects such as the HttpContext or the Session. However, these objects will be used by our code, so we will need something to represent the objects, these are the mocks.

Mock frameworks will create mock objects that we can use in place of the real objects normally used by the code. Typically with a mock framework we will call a method to create the mock, passing a reference to the type of thing to be mocked. These types are either interfaces or abstract base classes. The framework will then use reflection to create an instance of a mock class that implements the interface (or derives from the base class), that we can use in your code.

When testing ASP.NET we will need to mock up classes such as HttpContext, however, in the framework this is a concrete class. To address this issue, and to make the framework more testable, Microsoft has introduced ‘base’ classes, such as HttpContextBase into the MVC framework. These classes are abstract classes that we can use to mock up the objects we need.

Scott Hanselman, now of Microsoft, has developed some helper classes to work with mock frameworks.

From the MvcHelpers class we will use the MvcMockHelpers class for Rhino Mocks, and from this class we initially want to use the first method:

public static HttpContextBase
	FakeHttpContext(this MockRepository
	mocks)
{
	HttpContextBase context =
	mocks.PartialMock<HttpContextBase>();
	HttpRequestBase request =
	mocks.PartialMock<HttpRequestBase>();
	HttpResponseBase response =
	mocks.PartialMock<HttpResponseBase>();
	HttpSessionStateBase session =
		mocks.PartialMock<
		HttpSessionStateBase>();
	HttpServerUtilityBase server =
		mocks.PartialMock<
		HttpServerUtilityBase>();
	SetupResult.For(context.Request).
		Return(request);
	SetupResult.For(context.Response).
		Return(response);
	SetupResult.For(context.Session).
		Return(session);
	SetupResult.For(context.Server).
		Return(server);
	mocks.Replay(context);
	return context;
}
Notice that this is a C# 3.0 extension method. Extension methods are essentially static helper methods that appear to the compiler and editor as if they are instance methods of a class. Extension methods are defined as static methods of static classes, and are characterized by having their first parameter prefixed by the ‘this’ keyword. This parameter defines the class the methods are attached to, in this case the Rhino Mocks MockRepository class.

Given this definition we can use the MvcHelpers methods like this:

MockRepository mocks =
	new MockRepository();
BlogController controller =
	new BlogController();
mocks.SetFakeControllerContext(controller);
Notice that the call to SetFakeControllerContext looks like an instance method of the mocks object, whereas it is actually a call to the static method.

The call to SetFakeControllerContext creates a mock HttpContext that we can use with our tests. Notice that this call sets up a number of mock objects, including the request, response and session. SetFakeControllerContext also creates a new System.Web.Mvc.ControllerContext and sets that context on the controller we pass in. This means that when the controller asks for a reference to HttpContext or any of the other ASP.NET classes it will get a reference to the mock objects.

The controller will almost certainly render to a view. In ASP.NET MVC the view engine is pluggable. The default view engine will take the output from an aspx page and render it, reading the <% tags and the aspx controls. When testing we don’t want the view rendered, so we replace the view engine with a testing engine. The class for that would look something like this:

class FakeViewEngine : IViewEngine
{
	public ViewContext ViewContext {
		get; private set; }
	public void RenderView(
		ViewContext viewContext)
	{
		this.ViewContext = viewContext;
	}
}
This saves the viewContext for later use.

We can now write our test:

[TestMethod]
public void TestBlogController()
{
	BlogController controller =
		new BlogController();
	FakeViewEngine fakeViewEngine =
		new FakeViewEngine();
controller.ViewEngine = fakeViewEngine ;
	MockRepository mocks =
		new MockRepository();
	using (mocks.Record())
	{
		mocks.SetFakeControllerContext(
			controller);
	}
	using (mocks.Playback())
	{
		controller.New("Test", 1);
	}
}
Here we’re using the Visual Studio 2008 testing tools, but we could use NUnit or any other testing framework. At this point the test does nothing other than check that the mocks and the controller work together. We create an instance of the BlogController, set up the fake view and the mocked context, and call the New method; we’re not testing that the new method works correctly.

The way that mock tests work is that we first set our expectations of the code, then we check those expectations are met; mocks.Record sets the expectations and mocks.Playback ensures they have been met.

We typically use Record to set up the mocks, specify the order that methods are expected to be called and the results we expect from those methods. We can then use Playback to execute the code, if the expectations have not been met then an exception will be thrown.

Once we have this in place we can place some asserts in the code to ensure the testing works:

using (mocks.Playback())
{
	controller.New("Test", 1);
	Assert.AreEqual("New",
	fakeViewEngine.ViewContext.ViewName);
}
One of the primary areas where testing will be used is for database testing. In the code we have here we use LINQ to SQL to manage the data the application needs. To test code that uses LINQ to SQL we need to think about some design issues.

For our testing code to work we also have to carefully design the controller class. Many methods in the controller will use the BlogDataContext and initially the written code looks something like this:

public void Index(string blogTitle)
{
	BlogDataContext blogDataContext =
		new BlogDataContext();
	User user = HttpContext.User as User;
	BlogData data =
		blogDataContext.GetBlogEntries(
		blogTitle, user);
	RenderView("Index", data);
}
This code is hard to test. The BlogDataContext is a concrete class that will try and connect to the database. As we don’t want that when testing, we need to class to be standalone, and we want as little database access as possible (ideally none). This means we need to avoid creating the BlogDataContext.

The behaviour we want in the controller is that when called for real, it should use the real data context, but when called during testing it should use a test data context, ideally one that is mocked.

To do this we define the controller class with two constructors. The default constructor creates a real BlogDataContext and the other constructor is the one to which we can pass our mock data context. For this to work both the BlogDataContext and our testing data context need to derive from the same base class or interface, which would normally be problematic as BlogDataContext is generated code! Luckily, LINQ to SQL uses partial classes to define its datatypes, so we can extend the generated classes. We can create a partial BlogDataContext of our own and have that implement the interface:

public partial class BlogDataContext: IBlogDataContext
{
And we can create a mock object from the interface to pass to the controller.

The controller now looks like this:

IBlogDataContext blogDataContext;
public BlogController() : this (new BlogDataContext())
{}
public BlogController(IBlogDataContext dataContext)
{
	this.blogDataContext = dataContext;
}

public void Index(string blogTitle)
{
	User user = HttpContext.User as User;
	BlogData data = blogDataContext.
		GetBlogEntries(blogTitle, user);
	RenderView("Index", data);
}
We can define the interface like this:
public interface IBlogDataContext
{
	BlogData GetBlogEntries(string blogTitle, User user);
	bool AddBlogEntry(int blogid,
		string entryTitle, string entryText);
	bool DeleteBlogEntryById(int id);
	BlogEntry EditBlogEntry(int id);
	void UpdateBlogEntry(int id,
		string entryTitle, string entryText);
}
That just leaves the test code:
[TestMethod]
public void TestBlogControllerIndex()
{
	FakeViewEngine fakeViewEngine = new FakeViewEngine();
	BlogController controller;
	MockRepository mocks = new MockRepository();
	using (mocks.Record())
	{
		IBlogDataContext dataContext =
			mocks.CreateMock<IBlogDataContext>();
		controller = new BlogController(dataContext);
		mocks.SetFakeControllerContext(controller);
		SetupResult.For(dataContext.GetBlogEntries("", null)).
		IgnoreArguments().Return(new BlogData(){});
	}
	using (mocks.Playback())
	{
		controller.ViewEngine = fakeViewEngine;
		controller.Index("Some Title");
		Assert.AreEqual("Index",
	fakeViewEngine.ViewContext.ViewName);
		}
}
This code is more complicated than last time. But the primary pieces to notice are the calls to:
IBlogDataContext dataContext =
	mocks.CreateMock<IBlogDataContext>();
controller = new
	BlogController(dataContext);
…and:
SetupResult.For(dataContext.
		GetBlogEntries("", null)).
IgnoreArguments().Return(new BlogData());
In the first two lines we set up the data context we are going to use. Here we specify that we want to create a mock data context and pass that to the controller. Notice that as we are using an interface we could easily create an in-memory data context, or use a test database, so long as that code implements IBlogDataContext.

As we are using the mock data context, we have to tell the mock engine what to do when the code under test, in this case the call to controller.Index(), calls GetBlogEntries(). The second piece of code above says that when GetBlogEntries is called the mock should ignore the arguments and return a new un-initialized BlogData object. In this case our test simply asserts that the return view is correct, but we can test far more scenarios once we have the mock and the correct design in place.

Conclusion

The ASP.NET Model View Controller architecture provides two great new features that as a web developer I’m planning on taking advantage of.

The first is the extensive routing framework which provides a layer on which to build complex web applications.

The second is the ability to test the framework. Microsoft has put a lot of effort into designing the framework for testing. We should ensure that we put the same effort into our code.


Kevin Jones has been involved in software development and design for more years than he cares to remember, originally as a mainframe programmer in the days when “client-server computing” wasn’t called “client-server computing”. He has been involved in training and consultancy since 1990 and is the co-author of Servlets and JavaServer Pages: The J2EE Technology Web Tier. He is also a regular speaker at Bearpark’s annual Software Architect and DevWeek conferences.

You might also like...

Comments

About the author

Kevin Jones United Kingdom

Kevin Jones has been involved in software development and design for more years than he cares to remember, and has been involved in training and consultancy since 1990. He is the co-author of Se...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Better train people and risk they leave – than do nothing and risk they stay.” - Anonymous