Much of the early hype surrounding the ASP.Net MVC framework revolved around the ability to use your dependency injection/inversion of control container of choice for resolving controllers at runtime. The MvcContrib project already has implementations for Windsor, Spring, and StructureMap with ObjectBuilder soon to follow. The framework also allows developers to use view rendering technologies other than the built in WebForms ViewPage. The MvcContrib project already has an NVelocity view renderer thanks to Hammett. I'm writing this post to try and influence, Scott, Phil, Rob, and Eilon to take the extensibility to the next level.
What I am proposing is nothing radical and adds less than 10 lines of code to the existing implementation. This is the right time to throw out suggestions since this is only the first CTP and there are areas that are obviously 'under construction.' Even with all of the extensible points of the framework, I still see an awful lot of Activator.CreateInstance littered throughout the code. Take the IControllerFactory for instance. This is hailed as an extensibility point allowing you to supply your own implementation and thus have control over the creation of controllers. It does deliver on its promise however, the framework instantiates a controller factory per request. What if I want to reuse a controller factory? Or what if my controller factory has dependencies on other classes or logic? Why not let me, the developer, be in charge of providing you with an IControllerFactory when you need one? But how, you ask, can this be achieved without disrupting the space-time continuum? It's actually much simpler than you think.
The changes I propose affect HttpContextWrapper2. You know this is a work in progress because (I seriously hope) Microsoft would not ship a class with that name (MvcContext maybe?). As an aside, whatever you guys finally choose can you please not make it internal sealed? Is that really necessary? But I digress. So here are my changes to HttpContextWrapper2:
Before:
object IServiceProvider.GetService(Type serviceType)
{
return ((IServiceProvider) this._context).GetService(serviceType);
}
After:
object IServiceProvider.GetService(Type serviceType)
{
IServiceProvider provider = this.ApplicationInstance as IServiceProvider;
if( provider != null )
{
object service = provider.GetService(serviceType);
if( service != null )
{
return service;
}
}
return ((IServiceProvider)this._context).GetService(serviceType);
}
As promised, a very simple change to the existing implementation. But what did we gain? Well, we now give the ApplicationInstance a crack at providing the requested type, rather than just redirecting to the underlying HttpContext. What is the ApplicationInstance? In your run of the mill web application, this is the class housed in Global.asax.cs. So, with the code snippet below, we could use the Windsor container as the service provider:
public class Global : HttpApplication, IServiceProvider
{
private static WindsorContainer _container;
public object GetService(Type serviceType)
{
if( _container.Kernel.HasComponent(serviceType) )
{
return _container.GetService(serviceType);
}
return null;
}
protected void Application_Start(object sender, EventArgs e)
{
// Initialize the container...
}
}
Now the MVC framework can take advantage of the rich DI/IoC capabilities of Windsor (or any of the containers mentioned at the beginning of this article) without adding dependencies to it. Just replace all of those Activator.CreateInstance(type) calls with context.GetService(type). If the call to GetService returns null, then you can still resort to your own creation mechanisms. But at least give us a chance to take control of the process if we so choose.
Unless I a missing something this should be a low impact, low risk modification to the existing framework with high rewards.
posted @ Sunday, December 16, 2007 4:18 PM