Dependency Injection in ASP.NET MVC: Controllers


After working for 5 years with WebForms, it was quite a breath of fresh air to deal with simple controllers and actions with MVC.  Even better was that there is support for IoC containers built in to the framework.

However, support for dependency injection does not run very deep in an explicit manner, and some work is required to get your container to take as much control as possible over locating instances of objects.

Dependency injection is one of the most powerful tools in any advanced developer’s toolkit.  The ability to remove the responsibility of instantiating, locating and controlling lifecycle of dependencies both promotes good OO design and opens entirely new avenues of architecture that weren’t feasible before.

But to get started, DI needs to start at the topmost layer of your application, ideally the entry point through which everything else flows.  Unfortunately for us using ASP.NET MVC, there are several entry points, several of which are exposed, several of which are not.

Luckily for us, it’s still possible to ensure that we try and banish the “new” operator for instantiating services as much as possible.  Once we do, those new doors open up to a potentially much more powerful design.

First things first, we need to tackle our first entry point: controllers

The built-in controller factory

ASP.NET MVC uses a specific interface to control lifecycle of a controller, IControllerFactory:

public interface IControllerFactory {
    IController CreateController(
        RequestContext requestContext, 
        string controllerName);
    void ReleaseController(IController controller);
}

One interesting piece to note here is that this interface includes both the instantiation and releasing of the controller instance.  We’ll dive into some interesting applications of this in a future post, but we’ll just leave it alone for now.  The built-in controller factory is the DefaultControllerFactory class, which provides some simple out-of-the-box behavior for instantiation:

return (IController)Activator.CreateInstance(controllerType);

and releasing:

public virtual void ReleaseController(IController controller) {
    IDisposable disposable = controller as IDisposable;
    if (disposable != null) {
        disposable.Dispose();
    }
}

There’s some other behavior in there, as it provides a virtual member to retrieve a controller by type instead of by name (which is just a string).  Since the DefaultControllerFactory provides this string –> System.Type work for us, we’ll just use it.  But instead of the familiar Activator.CreateInstance call, we’ll use our container.

Service-located controllers

Service location is bad…just about 99% of the time.  But service location has to happen somewhere, and it’s ideally at the entry point of our application.  We’ll need to use service location for our controller factory, but that doesn’t mean we can’t still have some control over what gets used.

First, we’ll create our own custom controller factory, and inherit from DefaultControllerFactory.  One minor addition is that we’ll still supply the container use to our controller factory (using StructureMap in this example):

public class StructureMapControllerFactory : DefaultControllerFactory
{
    private readonly IContainer _container;

    public StructureMapControllerFactory(IContainer container)
    {
        _container = container;
    }

Once we have our container, we can now use it to override the GetControllerInstance method:

protected override IController GetControllerInstance(
    RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
        return null;

    return (IController)_container.GetInstance(controllerType);
}

We’ll leave the ReleaseController method alone (for now).  Finally, we just need to configure our controller factory in the global.asax implementation in Application_Start:

var controllerFactory = new StructureMapControllerFactory(ObjectFactory.Container);

ControllerBuilder.Current.SetControllerFactory(controllerFactory);

You’ll just need to make sure you configure your container before instantiating the controller factory.  So why pass in the container?  I can’t stand service location, as it’s a big roadblock to a lot of other interesting techniques.  Because I pass in the container to the controller factory, I now remove the responsibility of the controller factory of figuring out where to get the container.

So why not use something like Common Service Locator instead of using a container-specific interface?  Isn’t that coupling myself to a specific library?  What about depending on abstractions?

Common Service Locator is pretty much useless.  It’s very limiting in its interface, and only supports the least common denominator among the different containers out there.  Additionally, we’ll be using some of the more powerful features in modern containers soon, and just having the simple “GetInstance” method won’t be enough for what we’re looking for.

In the next post, we’ll integrating some more advanced usages of injection with our controllers, to gain control over the myriad of helper objects used throughout the controller lifecycle.

AutoMapper source moved to GitHub