Developer Notes for the ASP.NET MVC Framework

as presented at the alt.net conference

by
last update: January 7th 2008

The ASP.NET MVC Extensions

On October 6th 2007, in Austin, TX, Microsoft's General Manager Scott Guthrie, affectionately known as ScottGu unveiled the much rumored platform for web development. Since then, the MVC Extensions have been packaged and release in the form of the ASP.NET 3.5 Extensions CTP and is expected to be formally released in the first half of 2008, possibly included in the first service pack for .Net 3.5.

Along with the CTP release, an Open Source project called MVC Contrib has been started to provide more functionality for the MVC framework. This project also tries to provide alternative implementation for portions of the framework, based on other existing and popular open source projects.

When it was released in 2001, ASP.NET introduced the WebForm as an abstraction to dynamic HTML pages, which provided the developers a web development experienced that tried to mimic that of a desktop GUI application.

Fast forward 5 years and you would see a growing number of developers and architects dissatisfied with the kind of applications that WebForms led to. Namely, the low testability and low cohesion of the code written in the WebForm classes. These problems are increasingly relevant when developing and trying to maintain all but most simple web sites.

Around this same time a few alternative web development frameworks were getting more attention every day, most notably Ruby On Rails, Django, and the Rails-inspired CakePHP and MonoRail. The centerpieces of these frameworks are implementations of the Model-View-Controller pattern for the user interface process.

The MVC pattern is not a new concept and it is very well explained in several articles and all the major web development framworks have some form of a MVC implementation that can be used, many of which are Open Source. MVC is a tried and tested architecture for desktop and web applications and has proved to produce maintainable and scalable source code.

To give you a very simplified and rough explanation of the MVC pattern, the Model are your domain objects (User, Product, Company, Account, etc,) the View are the slice of your data that is formatted and made available to the user (usually via templates,) the Controller is the logic that receives the user's request for data, uses the Model to operate on and select data for the response, and then chooses the appropriate View to be delivered to the user. I know this is not the entire story and not 100% accurate but, like one of my favorite teachers used to say, "Teaching is like telling a smaller lie each day." Hang on.

Books that have helped me

.Net-specific or not, you may find these books useful when applying the MVC pattern to your ASP.NET application.

ASP.NET Comes of Age

The new MVC framework draws from the collective knowledge in the existing implementations (.Net or not) to help the ASP.NET developer create applications that sport the following important characteristics.

The MVC framework keeps the above promises by way of a design that strives to be interface-based, where virtually everything can be replaced with alternative implementations.

If you have tried to create unit tests for your ASP.NET user interfaces, you will understand why I was delighted to see the new interfaces, in special IHttpContext, IHttpRequest, and IHttpResponse.

By adopting a pluggable and open architecture Microsoft is accomplishing at least two very interesting goals.

As a testament to the good design of the existing ASP.NET platform (minus WebForms,) no changes were needed in ASP.NET to make the MVC framework a reality. The MVC functionality is mostly delivered by a new HttpModule, HttpHandler, and a core class library.

Enough of that. Show me the code

Let's pretend we are building part of an online banking application. Our users have accounts and they want to check their balances and make money transfers.

After installing the ASP.NET 3.5 Extensions, a few new soution templates will be added to Visual Studio 2008 for creating MVC applications. When we create our new MVC application, our project structure will look like this after we add some files:

\Banking
	\Content
		logo.png
		bank_lib.js
		help.html
	\Controllers
		AccountsController.cs
		LoansController.cs
	\Models
		Account.cs
		AccountsSqlDao.cs
		Loan.cs
		LoanSqlDao.cs
		Transaction.cs
		TransactionSqlDao.cs
	\Views
		\Accounts
			Balance.aspx
			  Balance.aspx.cs
			Recent.aspx
			AccountsList.aspx
		\Loans
			Apply.aspx
			List.aspx
			Payoff.aspx
			

As you can see, our code is conveniently organized in the appropriate folders, which are pretty much self-explanatory. One thing to keep in mind with MVC projects is that the folder structure no longer maps directly to the final URLs of your pages. Instead, there's a new mechanism called Routing that takes care of that.

Routing — connecting the pipes

To better explain how Routing works, lets define what URLs are available in our project. Assuming the AccountsController has methods called List, Balance and RecentTransactions, here's how we define the routes (or URL formats) of our application.

//in global.asax.cs
protected void Application_Start(object sender, EventArgs e)
{
	RouteTable.Routes.Add( new Route
		{
			Url = "/MyAccount/[action]/[accountNumber]",
			Defaults = {controller = "Accounts", accountNumber = (string)null}, 
			RouteHandler = typeof(MvcRouteHandler)
		});	
} 
			

The above code will produce URLs like:

There's quite a bit going on here. First, we are creating a new Route format for URLs that will start with MyAccount, followed by an Action name and an account number. Then we use the Defaults property of the route to tie this route to the AccountsController and default the accountNumber part of the URL optional and defaulting to null.

As you may have already guessed, the elements of the route URL wrapped in square brackets represent things that will be parsed out of the incoming URL in a process called tokenization.

The RouteHandler property of the route identifies which class will be used to produce the appropriate IHttpRequestHandler instance that will take care of the incoming request. This handler ultimately will, in turn, instantiate the controller and invoke the correct action with the correct parameters.
You may never need to replace the default MvcRouteHandler but it is good know that you have that possibility in an edge case.

Dispatching the data with controllers

Let's take a look at the AccountsController class now.

public class AccountsController : Controller
{	
	IAccountsDao Accounts = new AccountsSqlDao();
	ITransactionsDao Transactions = new TransactionsSqlDao();
	int userID = AppUser.Current.UserID;

	[ControllerAction]
	public void List()
	{
		ViewData["myAccounts"] = Accounts.GetAll(userID);
		RenderView("AccountsList");
	}

	[ControllerAction]
	public void Balance(string accountNumber)
	{
		ViewData["balance"] = Accounts.Find(userID, accountNumber).Balance;
		ViewData["accountNumber"] = accountNumber;
		RenderView("Balance");
	}

	[ControllerAction]
	[OutputCache(30)]
	public void RecentTransactions(string accountNumber)
	{
		ViewData["transactions"] = Transactions.GetRecent(
								userID, accountNumber);
		ViewData["accountNumber"] = accountNumber;
		RenderView("Recent");
	}

	private void SomeMethod(){ ... }
}
			

The methods marked with the ControllerActionAttribute in the AccountsController will become valid actions in the URLs that start with /MyAccount. Any extra parts of the URL path after the action name will be passed as arguments to the methods.

Our controller inherits from the abstract Controller class that comes with the MVC framework, but in reality it's only needed to implement the IController interface. Inheriting from Controller will give us a handy default implementation that will provide us a lot of the support code that enables the controller choose and feed the views.

Our actions are very skinny and that's how they should be. All they have to do is use model classes of your application to retrieve and/or modify domain objects.
The model code are not necessarily all code that you wrote, they can also be as simple as ADO.NET data access objects or some existing implementation like NHibernate, IBatis, LINQ to SQL, some other ORM, etc. In my example above we're using custom XxxxxSqlDao objects via IxxxxDao interfaces (think service layer).

Your controller classes will normally get an important piece of the MVC action (bad pun,) but be careful not to let your controllers outgrow their basic function of responding to user requests by leveraging the model objects. We should strive to keep the business rules contained inside the model objects because these are the objects more likely to be reused in other parts of your application.

All that said, your controllers may still use a number of other supporting classes to get the job done. These supporting classes can be for example a service layer (or DAO object like we see in the AccountsController,) a logging component, a rules engine, etc. To avoid the coupling between the controller code and these other classes we can use a Dependency Injection (DI) component explicitly like:

	
IAccountsController accounts = DIWrapper.GetInstance<IAccountsController>();
			

The MVC framework acknowledges this need and makes it even easier to leverage Dependency Injection in the controllers via the IControllerFactory interface. To leverage this interface we will need to create a small controller factory class, which will wrap our DI component of choice. Let's choose Castle Windsor as our DI component for the sake of the example.

	
public class WindsorControllerFactory<T> : IControllerFactory 
						  where T is IController
{
	WindsorContainer container = new WindsorContainer(new XmlInterpreter());

	public IController CreateController(RequestContext context, Type controllerType);
	{
		return (IController)container.Resolve(controllerType);
	}
} 
			

That seems pretty simple, but where is it used? Actually there are two simple ways of using it. You can set the factory for each controller individually or set the default factory for all controllers. We'll show the former. Add the following just under your RouteTable/Route code in gloal.asax.cs.

ControllerBuilder.Current.SetControllerFactory(typeof(AccountsController), typeof(WindsorControllerFactory));
			

By linking the controller type to the controller factory type, we are instructing the MVC framework to use the WindsorControllerFactory to instantiate all AccountsController objects. In our case the Windsor Container will take care of the actual instantiation of the object and passing the the appropriate parameters to the controller constructor or set the controller's properties with the right values.

After a little bit of refactoring our AccountsController is now looking like this:

public class AccountsController : Controller
{	
	public IAccountsDao Accounts {get; set;} //C# 3+ syntax
	ITransactionsDao Transactions  {get; set;} //C# 3+ syntax
	
	int userID = AppUser.Current.UserID;

	[ControllerAction]
	public void List() {/* same as before */}

	[ControllerAction]
	public void Balance(string accountNumber) {/* same as before */}

	[ControllerAction]
	[OutputCache(30)]
	public void RecentTransactions(string accountNumber) {/* same as before */}

	private void SomeMethod(){ ... }
}
			

Now we are no longer explicitly setting the private members Accounts and Transactions. They will be taken care of by the Windsor container and its configuration. We have just decoupled our controller from any particular implementation of our data access layer and that leads to more maintainable applications.

Views, the final destination

There were two important things in our AccountsController that we have not explained yet. First there's this ViewData hash (or dictionary) where we are placing our data and then the call to a RenderView method.

The ViewData is our primary way of providing data to our views (as if the name wasn't clear enough.) The same ViewData hash will be available for us when writing our view templates.

The RenderView method is how our controller action decides what is the appropriate view to be displayed to the user. Let's take the Balance action as an example.

	
[ControllerAction]
public void Balance(string accountNumber)
{
	ViewData["balance"] = Accounts.Find(userID, accountNumber).Balance;
	ViewData["accountNumber"] = accountNumber;
	RenderView("Balance");
}
			

Here we are using the data access object to retrieve the available balance in for the given account number. The returned value is stored in the "balance" item in the ViewData hash. The account number is also put in the ViewData. After that the controller only needs to choose a view to render that data (usually HTML) for the user.

When calling RenderView with a view name like we did, the MVC framework will default to the familiar ASPX rendering engine. In our case it will look for a file named Balance.aspx in the /Views/Products folder. This is a naming convention based on the controller's name. If Balance.aspx is not found in this directory, the framework will also look into the /Views/Shared directory.
It's possible to use other view engines to leverage things like NVelocity and Brail. This would be done by implementing the IViewFactory and IView interfaces, which we will not cover in this article.

The Balance.aspx file can be something like the following.

<%@ Page 
	Inherits="MvcApp1.Views.Balance" CodeBehind="Balance.aspx.cs"
	MasterPageFile="~/Demo.master" %>
<asp:Content ContentPlaceHolderID="MainArea" runat="server" ID="content1">
	<p>
		Account number: <%= ViewData["accountNumber"] %> <br/>
		Available balance: <%= ViewData["balance"] %> <br/>
	</p>
</asp:Content>
			

Pretty non-extraordinary, eh? Since we are still using the ASPX engine to render our HTML, we can still use its extensive functionality like Master Pages, User Controls, custom controls, Localization, Themes, etc. One thing we cannot have, though, are post backs.

What? No postbacks? Are you kidding me? How are we going to act on the user's interactions? Well, you've just said it: act. Actions are the responsibility of the controllers. This is where that logic should be, not in our views. Separation of concerns, remember?

Keep in mind that we will still have the rendering events of our server controls, like the DayRender on a calendar and ItemCreated/ItemDataBound in our GridViews. What we'll have to learn how to do without are the post back events like Click on a button, SelectedItemChanged on a drop-down, PageIndexChanged on grids, and so on.

Although it's not clear at this point, we expect that custom control developers will be able to encapsulate action-driven functionality but, differently than the existing code-behind events, the new approach should be concentrated in the controller code.

Also, don't be put off by all that <%= %> old-style syntax. There will be a host of MVC-specific server controls that can be used instead of the above syntax and helper methods.

Less strings please

By now you should have noticed that there's a lot of string keys being passed around and that can lead to too much guessing (alternative spelling for bug eggs in my book) and they tend to be forgotten when you need to rename your controller actions and parameters.

Luckily, the MVC framework makes clever use of the staticly typed languages and the new language features introduced with the .Net 3.5 batch of language updates.

To start off, let's replace our ViewData hash with a strongly typed object.

public class AccountViewData
{
	public string AccountNumber{get; set;}
	public decimal Balance {get; set;}
	public IList<Transaction> Transactions {get; set;}
}
			

Now we just need to create an object of the AccountViewData tye and give it to the view. We do that with a few changes to our Balance method:.

[ControllerAction]
public void Balance(string accountNumber)
{
	AccountViewData data = new AccountViewData();
	data.Balance = Accounts.Find(userID, accountNumber).Balance;
	data.AccountNumber = accountNumber;
	RenderView("Balance", data);
}
			

We don't need to change the Balance.aspx file and it will continue to work with the ViewData["balance"] hash, but if you want you can make the ViewData in the view also be strongly typed. Let's do that. Open the code-behind of the view at Balance.aspx.cs and change its base class from ViewPage to ViewPage<AccountViewData>:

	
public partial class Balance : VewPage<AccountViewData>
{
  // .. no changes here
}
			

And now we can open Balance.aspx and use the properties in ViewData:

	Account number: <%= ViewData.AccountNumber %> <br/>
	Available balance: <%= ViewData.Balance %> <br/>
			

You said this was all testable

We started all this emphasizing the testability of the MVC applications. Let's add some tests to our controller to begin with.

//NUnit framework is assumed here
[TestFixture]
public class AccountsControllerTests
{
	[Test]
	public void ShouldProvideAccountBalance()
	{
		AccountsController acctCtr = new AccountsController();
		TestViewEngine viewEng = new TestViewEngine();
		acctCtr.ViewEngine = viewEng;
		acctCtr.Accounts = new MockAccountsDao();
		acctCtr.Balance("12345");

		//check that the Balance view was chosen
		Assert.AreEqual("Balance", viewEngine.ViewName);
		
		//check that the view engine received the expected view data
		AccountViewData viewData = viewEng.GetViewData<AccountViewData>();
		Assert.AreEqual(987.65, viewData.Balance);
	}
}
			

There are a few points worth explaining in the above code.

With all that in place, all is left for us is invoke the method being tested (Balance) and verify that it still does what we expect it to do. In case you didn't notice let me put it out clear:

We did not need the ASP.NET runtime or database access to test our controller. A true unit test.

Conclusion

We believe Microsoft did the right thing by giving developers the choice of a true MVC solution, with all the benefits we just discussed. Not every application needs to be done using this framework but it's great to finally see support for such solid architecture in ASP.NET.

We also believe that the positive impact of a clean and testable infrastructure like this in our applications far outweights the additional effort to stratify our code.

The brilliance how the MVC framework incorporated concepts from other frameworks and at the same time leveraged the unique features of .Net warrants applause and excitement for what more could be coming out of Redmond in between Visual Studio releases.

This article is not finished yet and is based on pre-release software. If you find broken examples, incorrect information, broken links, etc., please and I'll try to fix it as soon as possible.