Setting up Inversion of Control (IoC) in ASP.NET MVC with Castle Windsor
Asked Answered
C

5

8

I'm going over Sanderson's Pro ASP.NET MVC Framework and in Chapter 4 he discusses Creating a Custom Controller Factory and it seems that the original method, AddComponentLifeStyle or AddComponentWithLifeStyle, used to register controllers is deprecated now:

public class WindsorControllerFactory : DefaultControllerFactory
{
    IWindsorContainer container;

    public WindsorControllerFactory()
    {
        container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));

        // register all the controller types as transient
        var controllerTypes = from t in Assembly.GetExecutingAssembly().GetTypes()
                              where typeof(IController).IsAssignableFrom(t)
                              select t;

        //[Obsolete("Use Register(Component.For<I>().ImplementedBy<T>().Named(key).Lifestyle.Is(lifestyle)) instead.")]
        //IWindsorContainer AddComponentLifeStyle<I, T>(string key, LifestyleType lifestyle) where T : class;
        foreach (Type t in controllerTypes)
        {
            container.Register(Component.For<IController>().ImplementedBy<???>().Named(t.FullName).LifeStyle.Is(LifestyleType.Transient));
        }
    }

    // Constructs the controller instance needed to service each request
    protected override IController GetControllerInstance(Type controllerType)
    {
        return (IController)container.Resolve(controllerType);
    }
}

The new suggestion is to use Register(Component.For<I>().ImplementedBy<T>().Named(key).Lifestyle.Is(lifestyle)), but I can't figure out how to present the implementing controller type in the ImplementedBy<???>() method. I tried ImplementedBy<t>() and ImplementedBy<typeof(t)>(), but I can't find the appropriate way to pass in the implementing type. Any ideas?

Chi answered 26/2, 2011 at 1:13 Comment(4)
Please don't use Sanders' book for anything Windsor-related, it's very outdated by now.Wearproof
@Mauricio Schaffer, I realize that some of the things are outdated, but there are some nifty gems that might be useful... in particular, using the Web.config to help resolve dependencies: e.g. providing a connection string that is needed to initialize a repository class (although I have not figured out how to do it yet).Chi
that's also outdated, I wouldn't recommend doing that.Wearproof
@Mauricio, perhaps not resolving dependencies, but at least using the connection string or the database name (in my other question's example).Chi
T
6

I'm doing this using the ControllerBuilder.SetControllerFactory and the code you can find in the open source project MvcContrib:

Global.asax.cs

protected void Application_Start()
{
    ...

    IWindsorContainer windsorContainer = new WindsorContainer();
    windsorContainer.RegisterControllers(Assembly.GetExecutingAssembly());
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(windsorContainer));

    ...
}

WindsorControllerFactory

public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IWindsorContainer _container;

    public WindsorControllerFactory(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException();
        }

        _container = container;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException();
        }

        if (!typeof(IController).IsAssignableFrom(controllerType))
        {
            throw new ArgumentException();
        }

        try
        {
            return (IController)_container.Resolve(controllerType);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException();
        }
    }

    public override void ReleaseController(IController controller)
    {
        IDisposable disposable = controller as IDisposable;

        if (disposable != null)
        {
            disposable.Dispose();
        }

        _container.Release(controller);
    }
}

WindsorExtensions (see MvcContrib)

public static class WindsorExtensions
{
    public static IWindsorContainer RegisterController<T>(this IWindsorContainer container) where T : IController
    {
        container.RegisterControllers(typeof(T));

        return container;
    }

    public static IWindsorContainer RegisterControllers(this IWindsorContainer container, params Type[] controllerTypes)
    {
        foreach (Type type in controllerTypes)
        {
            if (ControllerExtensions.IsController(type))
            {
                container.Register(Component.For(type).Named(type.FullName).LifeStyle.Is(LifestyleType.Transient));
            }
        }

        return container;
    }

    public static IWindsorContainer RegisterControllers(this IWindsorContainer container, params Assembly[] assemblies)
    {
        foreach (Assembly assembly in assemblies)
        {
            container.RegisterControllers(assembly.GetExportedTypes());
        }

        return container;
    }
}

ControllerExtensions (see MvcContrib)

public static class ControllerExtensions
{
    public static bool IsController(Type type)
    {
        return type != null
               && type.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)
               && !type.IsAbstract
               && typeof(IController).IsAssignableFrom(type);
    }
}
Tinstone answered 26/2, 2011 at 2:22 Comment(3)
so you don't specify ImplementedBy (in the RegisterControllers method)? No issues with that?Chi
@Lirik: No ;) The extension method for Castle.Windsor is taken from the MvcContrib project (mvccontrib.codeplex.com). I have updated my answer with a link to the file.Tinstone
@Lirik: I'm using the above code with Castle 2.5.2 and ASP.NET MVC 3 RTM and so far no issue. Anyhow I've just noticed, that the MvcContrib.Castle project is marked with [Obsolete] now. I'll need to check out if there's a more recommended way since I implemented Windsor the last time.Tinstone
H
2

You may also want to consider using the new installer option in the latest Windsor build. There is more documentation on Windsor's tutorial: http://stw.castleproject.org/Windsor.Windsor-tutorial-part-three-writing-your-first-installer.ashx

Hame answered 26/2, 2011 at 3:12 Comment(0)
D
1

There's a tutorial (in the works but 9 parts are already out) that discusses usage of Windsor in ASP.NET MVC here. That's the most up to date and covering most of the usual usage resource on the topic as far as I'm aware.

Delegacy answered 26/2, 2011 at 7:21 Comment(0)
Y
1

@Lirik, as an addition: drop your own custom IControllerFactory out if you use MVC3. Just register controllers with Windsor and implement IDependencyResolver with Windsor container inside.

Set your IDependencyResolver as MVC DependencyResolver and DefaultControllerFactory will automatically wire up controllers registered in container (via DependencyResolver).

Yate answered 26/2, 2011 at 7:30 Comment(5)
sorry, how do I set the DependencyResolver? (note that I'm still reading the book and learning, so I apologize if it should be obvious) I made this project from scratch... where would I expect to see the "my own" IControllerFactory? I set the WindsorControllerFactory in the Application_Start method, but there was no other IControllerFactory being set there prior to me setting WindsorControllerFactory.Chi
I tried to get some info on the IDependencyResolver and I some people suggest that we shouldn't implement one for Windsor.Chi
@Lirik, you don't even need to implement IDependencyResolver by yourself. CommonServiceLocator can be set as DependencyResolver. Just take the Windsor adapter for CSL and set it like that: DependencyResolver.SetResolver(new WindsorServiceLocator(windsorContainerInstance)); You don't need to set up a controller factory since DefaultControllerFactory is set out-of-the-box, by default.Yate
@Lirik, about DependencyResolver and Windsor. The problem Mike Hadlow is talking about seems to be with container and how it tracks the transient instances lifetime, not with DependencyResolver itself. I mostly used StructureMap and Ninject and never had an issue with that. But if there is an issue with Windsor, you can, as told in comments on Mike's post, explicitly define the lifetime for your controllers when registering them in IoC container.Yate
@Lirik, I discovered a bit more about the issue. It seems like an issue with objects themselves, not any kind of IoC container or DependencyResolver. You may have memory leaks even if you don't use your IoC container or resolver at all. If your case is somewhat special and you have your own IController implementation that is not IDisposable - you are open to all those memory issues Mike talks about. Default ControllerBase or Controller class is IDisposable so MVC should dispose that when it is no more needed.Yate
L
-1

something like:

public void Register(IWindsorContainer container)
    {
        Assembly.GetAssembly(typeof(ControllersRegistrarMarker)).GetExportedTypes()
                .Where(IsController)
                .Each(type => container.AddComponentLifeStyle(
                                      type.Name.ToLower(),
                                      type,
                                      LifestyleType.Transient));
    }

ControllersRegistrarMarker is just an empty class in your Controllers assembly

Ledger answered 26/2, 2011 at 1:23 Comment(1)
AddComonentLifeStyle is deprecated... I could use it, but I'd would rather use the container.Register(...) function as suggested by the comment associated with the deprecated function.Chi

© 2022 - 2024 — McMap. All rights reserved.