Tuesday, February 09, 2010

Maintainable MVC Series: Inversion of Control Container - StructureMap

This article is part of the Maintainable MVC Series.



To make your code testable the pattern to use is that of Dependency Injection. DI is nothing more than injecting all dependencies of a class instance through the constructor (or setters if you wish, but less intuitive when reading the code, and doesn't guarantee all dependencies are set). For example if you have a controller that uses a factory and a repository you can both inject them into the constructor as follows:



[csharp]
public class Controller
{
private readonly IFactory factory;
private readonly IRepository repository;

public Controller(IFactory factory, IRepository repository)
{
this.factory = factory;
this.repository = repository;
}
}
[/csharp]



When looking at the constructor you immediately see on which components this class depends. Using interfaces makes testing even more simple, because then we can easily mock the dependencies. Creating an instance of the controller will look as follows:



[csharp]
IFactory factory = new Factory();
IRepository repository = new Repository();

Controller controller = new Controller(factory, repository);
[/csharp]

Once your nesting of dependencies gets bigger, setting up your instances becomes quite cumbersome. This causes you to rethink before you add another dependency to a class. This hinders testability, so you would really want adding another dependency to be a piece of cake.



Enters Inversion Of Control container. The IOC container is some software that handles initialization of all your dependencies. StructureMap is on of the longest available ones for .Net. Others are Castle Windsor, Spring.NET, Unity, Ninject and others. What makes StructureMap very pleasant to work with is it's extra facilities for testing.



First an example of how you use StructureMap to initialize your instances.



To setup which classes to use for which interfaces you do the following initialization once:



[csharp]
ObjectFactory.Initialize(x =>
{
x.For<IFactory>().Use<Factory>();
x.For<IRepository>().Use<Repository>();
});
[/csharp]

Then every time you need an instance of your controller you can use the following code:



[csharp]
Controller controller = ObjectFactory.GetInstance<Controller>();
[/csharp]

StructureMap looks at the constructor of class Controller and sees it's dependent on an IFactory and IRepository. It knows which concrete classes to use for these interfaces, so it creates those first and then creates the Controller instance by injecting them into the constructor.



StructureMap in MVC



In a standard MVC project your controllers are required to have a parameterless constructor, which is used to initialize them. Of course this is in conflict with the Dependency Injection we want to use the make our code testable and easy to setup with StructureMap. However MVC is extensible on this point, and we can provide our own ControllerFactory, which looks as follows:



[csharp]
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(Type controllerType)
{
if (controllerType == null)
{
return base.GetControllerInstance(controllerType);
}

try
{
return ObjectFactory.GetInstance(controllerType) as Controller;
}
catch (StructureMapException exception)
{
// log an error with the exception and ObjectFactory.WhatDoIHave()
throw;
}
}
}
[/csharp]

In MVC 2 the signature of method GetControllerInstance looks a little different, so our StructureMapControllerFactory will be adjusted correspondingly:



[csharp highlight="3,4,8"]
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext,
Type controllerType)
{
if (controllerType == null)
{
return base.GetControllerInstance(requestContext, controllerType);
}

try
{
return ObjectFactory.GetInstance(controllerType) as Controller;
}
catch (StructureMapException exception)
{
// log an error with the exception and ObjectFactory.WhatDoIHave()
throw;
}
}
}
[/csharp]

This factory makes sure that StructureMap initializes your controllers. Now to make MVC use this factory we add the following code to the Global.asax.cs:



[csharp]
protected void Application_Start()
{
// initialize structuremap container
StructureMapBootstrapper.Bootstrap();

// let structuremap handle creating controllers
ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
}
[/csharp]

On the last line we set the factory. The first line calls the bootstrapper, which does the initialization of StructureMap.



[csharp highlight="9,10,11,12,13,14,15,16"]
public class StructureMapBootstrapper : IBootstrapper
{
private static bool hasStarted;

public void BootstrapStructureMap()
{
ObjectFactory.Initialize(x =>
{
x.For<ISessionHandler>().Use<SessionHandler>();
x.For<ITempDataHandler>().Use<TempDataHandler>();

x.ForSingletonOf<ICacheHandler>().Use<CacheHandler>();

x.AddRegistry<FactoryRegistry>();
x.AddRegistry<RepositoryRegistry>();
x.AddRegistry<ServiceRegistry>();
});
}

public static void Restart()
{
if (hasStarted)
{
ObjectFactory.ResetDefaults();
}
else
{
Bootstrap();
hasStarted = true;
}
}

public static void Bootstrap()
{
new StructureMapBootstrapper().BootstrapStructureMap();
}
}
[/csharp]

The highlighted ObjectFactory.Initialize part in the code above is the part that is custom for your project. Here you specify which concrete class to use for which interface. The initialization isn't very fast, but only performed once in production code. For testing purposes this is to costly, so the restart action is provided.



You can also see the lines with AddRegistry: the registries that are added are a way to group your code, because otherwise the bootstrapper can become quite long. A definition of a registry:



[csharp]
public class FactoryRegistry : Registry
{
public FactoryRegistry()
{
For<IInputModelFactory>().Use<InputModelFactory>();
For<IViewModelFactory>().Use<ViewModelFactory>();
}
}
[/csharp]

The specific StructureMap code in your project only consists of the BootStrapper, the registries and the ControllerFactory. It's best to try and avoid the use of ObjectFactory.GetInstance throughout your code as much as possible. It makes it harder to replace StructureMap if you want to, but more importantly it makes your code harder to test. One case where you'll probably won't manage without is in the case of attributes. The creation of attribute instances is done by MVC and needs a parameterless constructor as well. However we can't replace the factory in this case.



Unfortunately we need an extra use of ObjectFactory.GetInstance, but we can do it within the constructor to keep it testable:



[csharp]
public class CustomAttribute : ActionFilterAttribute
{
private readonly ISessionHandler sessionHandler;

public CustomAttribute()
: this(ObjectFactory.GetInstance<ISessionHandler>())
{
}

public CustomAttribute(ISessionHandler sessionHandler)
{
this.sessionHandler = sessionHandler;
}
[/csharp]

If a class is created by StructureMap it defaults to the constructor with the most parameters.



Testing with AutoMocker



Okay, on to the reason I prefer StructureMap as an IOC container: AutoMocker. The RhinoAutoMocker creates an instance of the class under test and creates a Rhino Mock object for all (interfaced) dependencies. No more creation of mock objects yourself. And even more important you can add another dependency to a class without having to fix all tests that create an instance.



A test fixture will look as follows:



[csharp highlight="21,27"]
[TestFixture]
public class AdvertsControllerTests
{
private RhinoAutoMocker<Controller> autoMocker;
private Controller controller;

[SetUp]
public void Setup()
{
StructureMapBootstrapper.Restart();

autoMocker = new RhinoAutoMocker<Controller>(MockMode.AAA);
controller = autoMocker.ClassUnderTest;
}

[Test]
public void CreatePostShouldCallFormHandlerForValidPost()
{
//Arrange
FormEditModel editModel = new FormEditModel();
autoMocker.Get<IFormHandler>().Expect(p => p.CreatePost(Arg<FormEditModel>.Is.Anything)).Return(true);

//Act
controller.CreatePost(editModel);

//Assert
autoMocker.Get<IFormHandler>().VerifyAllExpectations();
}
[/csharp]

The highlighted autoMocker.Get<IFormHandler>() lines retrieve a mock object and set expectations. So, per test you only have to set the dependencies neccessary for the code under test. A controller often has a lot of dependencies, but they're not used by every action.

11 comments:

  1. Thank you for writing this up, we've been in need of a good StructureMap/ASP.NET MVC documentation post. Now I finally have a place to point people to.

    ReplyDelete
  2. Nice article! It took me a while trying to figure out how to inject into MVC attributes. For now I'm just adopting ObjectFactory.GetInstance(). I hope there are better solutions.

    ReplyDelete
  3. Injection of MVC attributes:
    http://www.dominicpettifer.co.uk/Blog/40/dependency-injection-in-asp-net-mvc-2---part-3--custom-dataannotation-validationattributes

    ReplyDelete
  4. Great, I am in the middle of learning how to "best practice"-build larger web apps with StructureMap and MVC, and your article is timely.

    If I may, I am wondering if this article could have a Part II that highlights using the Common Service Locator? As I understand it, this would essentially add a wrapper to StructureMap, making your app even more "maintainable".

    ReplyDelete
  5. I don't have any experience with Common Service Locator myself, but Jimmy Bogard is in the middle of writing a series on Dependency Injection with MVC and has something to say about why Common Service Locator is not very useful.

    For myself, I don't see it as a real problem having this 'tight' coupling to StructureMap. As long as the use of the ObjectFactory is restricted to as few locations as possible it isn't very hard to replace it with another IOC container.

    And experience teaches me that in practice parts of your software are rarely replaced with leaving your interfaces intact. So that's not something I aim at when I'm talking about maintainability. For me, maintainability is all about Separation of Concerns, Single Reponsibility and last but not least, comprehensibility. The latter is extremely important when developing software in a team. Who remembers exactly why something was written as it was two years after the fact?

    ReplyDelete
  6. What version of StructureMap is this? Using 2.6.1, this doesn't work. There is no GetControllerInstance(Type controllerType) to override, only GetControllerInstance(RequestContext requestContext, Type controllerType).

    ReplyDelete
  7. If jou take a close look, just below the code for MVC 1 is the code for MVC 2; that's the one that uses GetControllerInstance(RequestContext requestContext, Type controllerType).

    ReplyDelete
  8. Oops! That will teach me to keep reading before posting... (embarrassed). Yes, this is MVC 2. Sorry, never mind.

    ReplyDelete
  9. I've implemented how you have it, but I keep getting an error in the GetControllerInstance:

    The controller for path '/Conversation/Scripts/jquery-1.4.1.js' was not found or does not implement IController.

    Being that I'm new to structuremap and mvc2, do you have any thoughts why it thinks a javascript should be an IController? Very weird.

    ReplyDelete
  10. Hi Mike, I've seen the same problem with non-existing favicon.ico as well (some browsers try to find it regardless if you've included it in your html). What IIS does it first looks if a url leads to a physical file. If it doesn't find a file it tries mvc routing. After the routing other registered handlers can step in. Of course you can change this handler order in IIS but it shouldn't be neccessary. Your url probably matches with the very greedy route {controller}/{action}/{id}, but why the physical file isn't served before mvc routing steps in I don't know. Probably it doesn't exist where you point to.

    For making sure mvc doesn't try to handle .js files you can add an ignoreroute when registering routes. However the default ignoreroutes in mvc only work for files in the root of your application. For the proper way to ignore certain filetypes all together take a look at Phill Haack's post about it:

    http://haacked.com/archive/2008/07/14/make-routing-ignore-requests-for-a-file-extension.aspx

    By the way, a good way to include your javascript files is by using Url.Content, because it resolves the tilde (~) to the proper url, whether your site resides in the root of your domain or a subdirectory:

    [csharp]
    <script type="text/javascript" src="<%= Url.Content("~/Scripts/jquery-1.4.1.js") %>"></script>
    [/csharp]

    ReplyDelete
  11. Thanks so much for the update! And the crazy thing is that I put that in, and i'm still getting the error. Maybe i missed a reference some where.

    ReplyDelete