Enabling Windsor Integration in MonoRail

I recently wanted to take on old MonoRail application and update it to use Windsor for dependency injection (DI).  The application stated as a sort of prototype and slowing grew into a decent sized application.  There's a couple of places that I want to add some unit tests and I could really benefit from DI.  So I sat down to hook Windsor into the application.

There's already documentation on how to integrate Windsor into MonoRail.  After all, they kind of "grew up" together.  However, the documentation is a little old and MonoRail has gone through a number of changes (some breaking) and improvements.  The "using MonoRail" site (a sort of wiki-like site) had some information on integrating Windsor into MonoRail which was more up-to-date than the Castle docs, but still left out an important section about ViewComponents.  So I thought I'd bring everything I learned into a single blog post.  I plan to take much of this and use it to update the Castle docs as well as the "using" site.

Requirements

You'll need to reference a few extra assemblies in your MonoRail application:

  • Castle.DynamicProxy2.dll
  • Castle.MicroKernel.dll
  • Castle.Windor.dll
  • Castle.MonoRail.WindsorExtension.dll

Custom Container

The first thing is to create a sub-class of the Windsor container for your MonoRail application.  This specialized version of the container will add the following capabilities:

  1. It will add a new facility which will automatically configure our Controllers for a transient lifestyle (one instance per request).  Windsor's default lifestyle is Singleton (imagine having only a single instance for each controller – yikes!!).
  2. It will automatically register all of our controllers for us.  This is optional as you can individually register each controller, but I find the automatic registration much easier.
  3. It will automatically register all of our ViewComponents.  Like controllers, this could be done individually, but this is easier.

Our custom container is really quite simple.  We'll create WebAppContainer.cs in our App_Code directory.  Our constructor will call the base class constructor that will initialize the container from the config file (web.config):

using Castle.Core.Resource;
using Castle.Windsor.Configuration.Interpreters;
using Castle.MonoRail.WindsorExtension;
using Castle.Windsor;
using Castle.MicroKernel.Registration;
using Castle.MonoRail.Framework;
using Castle.Core;
 
public class WebAppContainer : WindsorContainer
{
    public WebAppContainer()
        : base(new XmlInterpreter(new ConfigResource()))
    {
    }
 
    public void Init()
    {
    }
}

Now let's work on our 3 items above.  We'll plug all of this into our Init() method which we'll call later when we initialize our container.

First, we add a facility for MonoRail integration.  This facility is part of MonoRail:

AddFacility("rails", new MonoRailFacility());

Next, we register all of our controllers (yes, in one statement).  My controllers are in a separate assembly so this is how my registration looks:

Register(
    AllTypes.Of<IController>()
    .FromAssemblyNamed("YourAssemblyName.Controllers"));

If your controllers are directly in your web application, you can change "FromAssemblyNamed(…)" to "FromAssembly(Assembly.GetExecutingAssembly())".

Now we also need to register our ViewComponents.  This registration will also make sure that the ViewComponents are set up with a transient lifestyle:

Register(
    AllTypes.Of<ViewComponent>()
        .FromAssemblyNamed("YourAssemblyName.Controllers")
        .Configure(cr => cr.Named(cr.ServiceType.Name).LifeStyle.Is(LifestyleType.Transient))
    );

That's it for the container!  Our complete, customized container looks like this:

using Castle.Core.Resource;
using Castle.Windsor.Configuration.Interpreters;
using Castle.MonoRail.WindsorExtension;
using Castle.Windsor;
using Castle.MicroKernel.Registration;
using Castle.MonoRail.Framework;
using Castle.Core;
 
public class WebAppContainer : WindsorContainer
{
    public WebAppContainer()
        : base(new XmlInterpreter(new ConfigResource()))
    {
    }
 
    public void Init()
    {
        AddFacility("rails", new MonoRailFacility());
 
        Register(
            AllTypes.Of<IController>()
            .FromAssemblyNamed("YourAssemblyName.Controllers"));
 
        Register(
            AllTypes.Of<ViewComponent>()
                .FromAssemblyNamed("YourAssemblyName.Controllers")
                .Configure(cr => cr.Named(cr.ServiceType.Name).LifeStyle.Is(LifestyleType.Transient))
            );
    }
}

Integrating Our Container

Now we need to hook this container into our web application.  We need to plug this into our custom HttpApplication (usually, your GlobalApplication.cs).  First, we need to add the IContainerAccessor interface:

public class GlobalApplication : HttpApplication, IContainerAccessor

Now add a static class variable to old our container:

private static WebAppContainer container;

Now we need to create the container when our application first starts and dispose of it when the application ends.  This is easy by hooking into the Application_OnStart and Application_OnEnd events.  The OnStart event is where we call the container's Init() method.

public void Application_OnStart()
{
    container = new WebAppContainer();
    container.Init();
}
 
public void Application_OnEnd()
{
    container.Dispose();
}

Finally, we need to implement the IContainerAccessor.Container property to return our custom container:

public IWindsorContainer Container
{
    get { return container; }
}

We're almost there!

Web.config Changes

Now that we're using Windsor, we need to configure our web.config.  Add the following section handler declaration in the <configSections> node:

<configSections>
    ..
    <section name="castle"
             type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
    ..
</configSections>

And add a "useWindsorIntegration" attribute to your existing <monorail> node, setting its value to "true":

<monorail useWindsorIntegration="true">

Since you probably don't have any components to register (since you haven't been using Windsor until now), you can add an empty <castle> section.  When you're ready to configure your components to enable DI and loose coupling, see the Windsor Configuration Reference at the Castle Project site.

That's It!

You're done.  We added a new, 33-line WebAppContainer.cs, added about 6-8 lines to GlobalApplication.cs and added a few lines to web.config.  Now we've got the full power of Castle's dependency injection to allow us to build loosely-couple, easily testable controllers and view components.

Technorati Tags: ,,,

No Comments