Contextual controller injection in ASP.NET MVC with Castle Windsor

I chatting the other day with Chris Canal, who played with reimplementing piece of code Jimmy Bogard put on his blog using Castle Windsor.

While I’m not an ASP.NET MVC expert by no means, I decided to give it a go and try to do it myself. See Jimmy’s post first to get the context, as I’m only going to talk about the code.

The code here uses development build of Windsor (what will become version 2.5) and does not work with version 2.1

No child container

Jimmy used child container to scope the contextual dependencies. While this is also possible using Windsor, it feels like using shotgun to kill a fly. We can use contextual meta-components that will have the sole purpose of scoping our RequestContext and ControllerContext and achieve the same effect as if we were using child container, only this is far more lightweight.

public class ControllerContextHost

{

    private ControllerBase controller;

 

    public void SetController( ControllerBase controller )

    {

        if( controller != null )

        {

            throw new InvalidOperationException( "Controller was already set!" );

        }

        this.controller = controller;

 

    }

 

    public ControllerContext GetContext()

    {

        if( controller == null )

        {

            return null;

        }

        return controller.ControllerContext;

    }

}

 

public class RequestContextHost

{

    private RequestContext context;

    public void SetContext( RequestContext context )

    {

        if( context != null )

        {

            throw new InvalidOperationException( "Context was already set!" );

        }

        this.context = context;

    }

    public RequestContext GetContext()

    {

        if( context == null )

        {

            throw new InvalidOperationException( "Context was not set!" );

        }

        return context;

    }

}

Having that we can now simply use the hosts to cache their respective components. We set the RequestContext in ControllerFactory.

public IController CreateController( RequestContext requestContext, string controllerName )

{

    var host = this.container.Resolve<RequestContextHost>();

    host.SetContext( requestContext );

    return container.Resolve<IController>( controllerName );

}

Registration

ControllerContext has a twist. Since we register controllers as transient, we have to pass the controller to ControllerContextHost right after it gets created. We use OnCreate method for that.

public void Install(IWindsorContainer container, IConfigurationStore store)

{

    container.Register(AllTypes.FromAssemblyContaining<HomeController>()

                            .BasedOn<IController>()

                            .Configure(x => x.LifeStyle.Transient

                                                    .Named(GetControllerName(x.Implementation))

                                                    .OnCreate((k, c) => k.Resolve<ControllerContextHost>()

                                                                            .SetController(c as ControllerBase))));

}

It is also important how we register the services:

public void Install( IWindsorContainer container, IConfigurationStore store )

{

    container.Register( Component.For<ControllerContextHost>().LifeStyle.PerWebRequest,

                        Component.For<RequestContextHost>().LifeStyle.PerWebRequest,

                        Component.For<RequestContext>()

                            .UsingFactoryMethod( k => k.Resolve<RequestContextHost>().GetContext() ),

                        Component.For<HttpContextBase>()

                            .UsingFactoryMethod( k => k.Resolve<RequestContext>().HttpContext ),

                        Component.For<Func<ControllerContext>>()

                            .UsingFactoryMethod<Func<ControllerContext>>(

                                k => k.Resolve<ControllerContextHost>().GetContext ) );

}

Both hosts are registered as per web request since that’s how we want to cache the contexts they host. We’re only going to use the hosts internally and not expose them. Hence we register the contexts via UsingFactoryMethod. We also don’t register the ControllerContext directly, but rather via Func delegate, to break cyclic dependency between ControllerContext and Controller.

Now we only need to register the remaining services we want to use:

public void Install( IWindsorContainer container, IConfigurationStore store )

{

    container.Register(

        Component.For<IFoo>().ImplementedBy<Foo>().LifeStyle.Transient,

        Component.For<IActionInvoker>().ImplementedBy<ControllerActionInvoker>().LifeStyle.Transient,

        Component.For<ITempDataProvider>().ImplementedBy<SessionStateTempDataProvider>().LifeStyle.Transient,

        Component.For<RouteCollection>().Instance( RouteTable.Routes ),

        Component.For<UrlHelper>().LifeStyle.Transient );

}

and we’re free to take dependency on IFoo, which has dependency on our contextual services. Just for completeness, here’s code registering all the components in the container:

this._windsorContainer = new WindsorContainer()

    .Install( FromAssembly.This() );

And now, assuming I didn’t mis-translate Jimmy’s StructureMap code, out application should work just like his sample.

Comments

Craig Neuwirt says:

Look nice!

I think these neeed to be registered transiently

Component.For<RequestContext>()
.UsingFactoryMethod( k => k.Resolve<RequestContextHost>().GetContext() ),
Component.For<HttpContextBase>()
.UsingFactoryMethod( k => k.Resolve<RequestContext>().HttpContext ),
Component.For<Func<ControllerContext>>()
.UsingFactoryMethod<Func<ControllerContext>>(
k => k.Resolve<ControllerContextHost>().GetContext ) );

@Craig

Why not per web request? Just to be safe and allow factories to scope their lifetime? Or is there a better reason.

Craig Neuwirt says:

PWR is better choice. I picked transient since they all used factory methods based on resolves so transients would defer to the underlying lifestyle

ok, I though there might be some MVC quirk I didn’t know of that made that a bad choice. Anyway i think you’re right – I should have used Transient of components that have their lifetimes scoped by other components.