Thursday, March 13, 2008

Windsor IoC Container and MVC Framework CTP 2

I spent all morning upgrading my current MVC project to use the MVC Framework CTP 2. It went mostly OK. There are a few funnies, like the way the arguments to Html.Form() extension method's parameters have been reversed and the changes to Html.ActionLink, but generally speaking it was pretty smooth. All the stuff that was in the MvcToolkit is now in the System.Web.Mvc assembly, so I was able to remove that reference from my project. MvcContrib has been updated for CTP 2, but the source is in a branch, so you shouldn't just get the latest trunk like I did initially (Duh!) Go here instead: http://mvccontrib.googlecode.com/svn/branches/MVCPreview2/. I checked it out and it built no problem. Then it was a simple case of dropping in the new assemblies.

I'm using Castle Windsor in my MVC project which means I use the WindsorControllerFactory from MvcContrib rather than the default controller factory. One of the things that has changed is the signature of the IControllerFactory's CreateController method, it now takes a string of the controller name rather than its type. I can see why this makes more sense because your controller factory might not have the same idea about the type it wants to supply as the routing framework. This means that we have to use the string ID of the controller that's registered with windsor rather than it's type. I had to make a small change in the code of the WindsorControllerFactory to fix a casing bug (URLs aren't case sensitive, but the windsor service ids are):

public IController CreateController(RequestContext context, string controllerName)
{
 //Hack...
 controllerName = controllerName.ToLower() + "controller"; 

 IWindsorContainer container = GetContainer(context);
 return (IController)container.Resolve(controllerName);
}

That means you also have to register the controllers with lower case names. Here's my InitializeWindsor method in my global.asax file:

protected virtual void InitializeWindsor()
{
    if (container == null)
    {
        // create a new Windsor Container
        container = new WindsorContainer(new XmlInterpreter("Configuration\\Windsor.config"));

        // automatically register controllers
        Assembly.GetExecutingAssembly().GetExportedTypes()
            .Where(type => IsController(type))
            .Each(type => container.AddComponentWithLifestyle(
                type.Name.ToLower(), 
                type, 
                Castle.Core.LifestyleType.Transient));

        // set the controller factory to the Windsor controller factory (in MVC Contrib)
        ControllerBuilder.Current.SetControllerFactory(typeof(WindsorControllerFactory));
    }
}

private bool IsController(Type type)
{
    return typeof(IController).IsAssignableFrom(type);
}

The .Each extension method is a little addition to Linq that a lot of people have blogged about, including me in this post.

Update

Jeremy Skinner, who's responsible for the WindsorControllerFactory has updated it in MvcContrib. He's also added some extension methods for the windsor container to register controllers, so now my InitializeWindsor method looks like this:

protected virtual void InitializeWindsor()
{
    if (container == null)
    {
        // create a new Windsor Container
        container = new WindsorContainer(new XmlInterpreter("Configuration\\Windsor.config"));

        // automatically register controllers
        container.RegisterControllers(Assembly.GetExecutingAssembly());

        // set the controller factory to the Windsor controller factory (in MVC Contrib)
        ControllerBuilder.Current.SetControllerFactory(typeof(WindsorControllerFactory));
    }
}

3 comments:

Jeremy said...

Hi Mike,

I've changed the WindsorControllerFactory to use lowercase controller names for the keys and I've also added some extension methods to IWindsorContainer for registering controllers as per your suggestion on the aspnet forums.

Mike Hadlow said...

Wow, that was quick work! Thanks Jeremy.

Anonymous said...

Hi,

I don't know if you ran into the Parameterless constructor exception, but the quick work around was:

IControllerFactory controllerFactory = new WindsorControllerFactory(container);

ControllerBuilder.Current.SetControllerFactory(controllerFactory);

And thanks for the great article :)