in

ASP.NET Weblogs

A Recipe for New Media

My Social Media Adventure #2 -- Using StructureMap as a Controller Factory in ASP.NET MVC

 

 The beautiful thing about ASP.NET MVC is that is contains several extension points (in part #1 we saw how the Model Binder extensions work).  Another extension point that is of great value is the ControllerFactory.  By default, ASP.NET MVC simply looks for a class called {ControllerName}Controller.  So a URL like this:  http://localhost/Home/Index would look for a controller called HomeController because Home is the controller name.  Under the default model factory, you must have a default constructor on your Controller class (you don't actually have to write one as C# will create a default one for you, but if you create a non-default constructor for use in TDD tests, you must explicity write a default constructor, for an example of this concept, take a look at the AccountController that is part of the ASP.NET MVC template project in Visual Studio 2008/2010).  This is fine for simple projects, but once you start getting into more complex projects that utilize services / data access patterns / repositories / etc, you could find yourself writing a lot of redundant code.

That is why Dependency Injection / Inversion of Control came about.  By using a DI container like StructureMap, you can decouple the services from the controllers and let the DI container worry about them.  So let's start by creating a class that implements the Controller Factory:

 

public class StructureMapControllerFactory : DefaultControllerFactory
{
public StructureMapControllerFactory(IContainer container)
{
this.container = container;
}

protected override IController GetControllerInstance(Type controllerType)
{
try
{
return container.GetInstance(controllerType) as Controller;
}
catch (StructureMapException)
{
System.Diagnostics.Debug.WriteLine(container.WhatDoIHave());
throw;
}
}

public override void ReleaseController(IController controller)
{
base.ReleaseController(controller);
}

public IContainer container;
}

That's it.  StructureMap has the ability that the first time it sees a new class, it will auto-generate an injection scheme based on the currently registered services.  Let's look at our Global.asax.cs file and see how we use this class:

protected void Application_Start()
{
Container container = new StructureMap.Container();

container.Configure(x => {
x.ForRequestedType<IRepository>()
.CacheBy(InstanceScope.HttpContext)
.TheDefault.Is.OfConcreteType<NHibernateRepository>();
});

ControllerBuilder.Current.SetControllerFactory(
new StructureMapControllerFactory(container)
);
}

Basically what we have done here is:

  1. Setup StructureMap.  Through we could have used the static class ObjectFactory and made all of our services globally available, I prefer to isolate the services within a private container.
  2. Register the NHibernateRepository class as the implementer of the IRepository pattern.
  3. Set the StructureMap Controller Factory as the default factory for finding controllers.

We would then rewrite our HomeController to support the IRepository pattern for data lookup as follows:

public class HomeController {
private IRepository repository;

public HomeController(IRepository repo) {
this.repository = repo;
}

public ActionResult Index() {
var items = repository.GetItems();
}

}

Every time a call was made that used the HomeController, StructureMap would new up a HomeController class, passing in the NHibernateRepository as the parameter to the constructor.  You no longer have to worry about the implementation details of the repository, in fact you could months from now decide to change from using NHibernate to Entity Data Model and all you would have to do is change the one line in your Global.asax.cs that sets the concrete implementation of IRepository to your new repository class, like so:

x.ForRequestedType<IRepository>()
.CacheBy(InstanceScope.HttpContext)
.TheDefault.Is.OfConcreteType<EdmRepository>();

 

And every single controller in your project that depended on IRepository will start using the Entity Data Model version of your repository without any further changes.

I will talk about the implementation details of the NHibernateRepository next, it will be a multipart treatment as there is a lot of details in the actual implementation as I use Fluent NHibernate, StructureMap as BytecodeProvider (to provide DI to the data model), and LINQ to NHibernate behind the scenes.  I will also discuss some performance enhancements I made to the repository.

Comments

No Comments

Leave a Comment

(required)  
(optional)
(required)  
Add