Dependency Injection in ASP.NET MVC: Contextual controller injection
In the last post, we looked at the basic DI usage in ASP.NET MVC – instantiating controllers. However, there are quite a few other things we can do with controllers besides merely instantiate them. One thing to keep in mind with the Controller base class is that although you can inject your controller’s dependencies, quite a few others are around via properties.
For example, the IActionInvoker and ITempDataProvider are two replaceable services the base Controller class uses to do its work, but to replace these items with your own behavior you usually need to rely on either an inheritance hierarchy or doing service location in a custom controller factory.
So why would you want to replace these items? For one, the IActionInvoker is basically the entire action execution pipeline, with all the filter usage and what not. If you wanted to extend this pipeline or change it, you’d want to replace the IActionInvoker (or extend the default ControllerActionInvoker).
I don’t like this all hard-wired in to the controller factory, because hey, that’s what a container is for!
###
Property injecting the remaining dependencies
If we want to supply the property-injected dependencies to our controllers, we first need to configure the container to supply the correct instances. With StructureMap, that’s pretty straightforward in our custom registry:
For<IActionInvoker>().Use<ControllerActionInvoker>(); For<ITempDataProvider>().Use<SessionStateTempDataProvider>(); For<RouteCollection>().Use(RouteTable.Routes); SetAllProperties(c => { c.OfType<IActionInvoker>(); c.OfType<ITempDataProvider>(); });
We first set up the implementations with the For..Use syntax. Next, we need to tell StructureMap to look for specific properties to inject, with the SetAllProperties method. We only want to set members of these two types, all others we’ll leave alone. The last For() method around the RouteCollection is important, we’ll use it in a bit for some of the various XyzHelper types.
With that configuration in place, we don’t have to touch our factories or our controllers to modify the property-injected services. We only need to modify our container configuration (which is the whole point of inversion of control).
Nested containers for contextual objects
One of the more advanced features of modern IoC containers is the concept of nested containers. Nested containers are built from an existing container, but can be configured independently so that you can configure items that exist only for the context of that container instance. Because a nested container is built from an existing container, it inherits the configuration of the parent container.
With ASP.NET MVC, that means that many of the contextual items of a request can be configured to be injected for a controller’s services, as opposed to passed around everywhere. For example, the ControllerContext object is almost ubiquitous in the execution of a controller, and is found just about everywhere.
Instead of passing it around constantly (whether it’s needed or not), we can instead take the contextual objects as a constructor dependency, and configure our nested container to supply the correct contextual objects. So what kinds of things can we supply, on demand as needed?
- RequestContext
- HttpContextBase
- ControllerContext
The last one is a bit tricky, as a ControllerContext is built from a Controller, so we can only supply a Func
So let’s look at how we’d create our controllers now:
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { var nestedContainer = _container.GetNestedContainer(); requestContext.HttpContext.Items[_nestedContainerKey] = nestedContainer; ControllerBase controllerBase = null; Func<ControllerContext> ctxtCtor = () => controllerBase == null ? null : controllerBase.ControllerContext; nestedContainer.Configure(cfg => { cfg.For<RequestContext>().Use(requestContext); cfg.For<HttpContextBase>().Use(requestContext.HttpContext); cfg.For<Func<ControllerContext>>().Use(ctxtCtor); }); var controller = (IController)nestedContainer.GetInstance(controllerType); controllerBase = controller as ControllerBase; return controller; }
We first create a nested container from the container passed in to our controller factory. Next, we stick the nested container instance into the HttpContext items, as we’ll need to dispose the container when we release the controller later. We build up a Func
On the other side, we want to configure the ReleaseController method to dispose of our nested container:
public override void ReleaseController(IController controller) { var controllerBase = controller as Controller; if (controllerBase != null) { var httpContextBase = controllerBase.ControllerContext.HttpContext; var nestedContainer = (IContainer)httpContextBase.Items[_nestedContainerKey]; if (nestedContainer != null) nestedContainer.Dispose(); } base.ReleaseController(controller); }
The only change we need to do is pull the nested container our from the controller’s context items. The only strange piece I ran in to here is that the GetControllerInstance accepts a RequestContext, but I only have an IController to work with in the ReleaseController method. In any case, we pull out the nested container, and if it exists, dispose it.
Using contextual items
To use our contextual items, we only need to make sure our controller depends on a service that uses them:
public class HomeController : Controller { private readonly IFooService _fooService; public HomeController(IFooService fooService) { _fooService = fooService; }
At construction time, HomeController is built with the nested container, which means all the contextual configuration rules will be used. That means our FooService implementation (and anything else it uses) can use any contextual item now, as long as it’s exposed as a dependency:
private readonly RequestContext _requestContext; private readonly HttpContextBase _httpContext; private readonly UrlHelper _helper; private readonly Func<ControllerContext> _context; public FooService( RequestContext requestContext, HttpContextBase httpContext, UrlHelper helper, Func<ControllerContext> context ) { _requestContext = requestContext; _httpContext = httpContext; _helper = helper; _context = context; }
Note that third item. Because I’ve configured both RequestContext and RouteCollection, StructureMap is now able to build a UrlHelper as a dependency:
public UrlHelper(RequestContext requestContext, RouteCollection routeCollection) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (routeCollection == null) { throw new ArgumentNullException("routeCollection"); } RequestContext = requestContext; RouteCollection = routeCollection; }
Having these request items around is nice, especially if you want to do things like generate URLs based on the current request context and routes, but don’t want to pass around the helper objects everywhere. With a nested container, you can configure items in the container that are only resolved for that container instance, giving you the ability to depend on any one of the typical context objects created during a request.
This is a bit more advanced usage of a container, and you can begin to see why we can’t use things like Common Service Locator in this instance. For a while, the differentiating factor in IoC containers was their registration abilities. These days, even object resolution is different among containers, with things like nested/child containers, instance swapping, auto-factories, Lazy
Next, we’ll look at some of the other items the Controller class new’s up and completely remove the service location/opaque dependencies inherent in the design of the ControllerBase class.