Castle Windsor ApiController Factory implementation for ASP.NET Web API
Asked Answered
O

6

9

I know it's possible to use DependencyResolver and register Castle Windsor with MVC but due to the issues described in https://mcmap.net/q/438300/-castle-windsor-dependency-resolver-for-mvc-3 we have stuck to the WindsorControllerFactory method of implementation on our MVC projects.

However it looks like the ApiControllers are using some other kind of factory as Castle Windsor is unable to inject the dependencies.

Has anyone figured out how to use Castle Windsor with ASP.NET Web API and MVC without using the DependencyResolver?

Ottilie answered 4/3, 2012 at 19:3 Comment(1)
O
7

Thanks to Critiano's post and some searching on-line I managed to get it to work here's the code for anyone else having this issue. I've fgot it working with MVC3 and ASP.NET Web Api Beta but I think the same solution should work for MVC4.

Firstly I created a WindsorHttpControllerFactory as the ApiControllers use a different factory than the MVC ones.

public class WindsorHttpControllerFactory : IHttpControllerFactory
{
    private readonly IKernel kernel;
    private readonly HttpConfiguration configuration;

    public WindsorHttpControllerFactory(IKernel kernel, HttpConfiguration configuration)
    {
        this.kernel = kernel;
        this.configuration = configuration;
    }

    public IHttpController CreateController(HttpControllerContext controllerContext, string controllerName)
    {
        if (controllerName == null)
        {
            throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", controllerContext.Request.RequestUri.AbsolutePath));
        }

        var controller = kernel.Resolve<IHttpController>(controllerName);
        controllerContext.Controller = controller;
        controllerContext.ControllerDescriptor = new HttpControllerDescriptor(configuration, controllerName, controller.GetType());

        return controllerContext.Controller;
    }

    public void ReleaseController(IHttpController controller)
    {
        kernel.ReleaseComponent(controller);
    }
}

The tricky part was registration it seems to involved registering a whole bunch of other stuff. This is what I ended up with.

container.Register(Component.For<IHttpControllerFactory>().ImplementedBy<WindsorHttpControllerFactory>().LifeStyle.Singleton);
container.Register(Component.For<System.Web.Http.Common.ILogger>().ImplementedBy<MyLogger>().LifeStyle.Singleton);
container.Register(Component.For<IFormatterSelector>().ImplementedBy<FormatterSelector>().LifeStyle.Singleton);
container.Register(Component.For<IHttpControllerActivator>().ImplementedBy<DefaultHttpControllerActivator>().LifeStyle.Transient);
container.Register(Component.For<IHttpActionSelector>().ImplementedBy<ApiControllerActionSelector>().LifeStyle.Transient);
container.Register(Component.For<IActionValueBinder>().ImplementedBy<DefaultActionValueBinder>().LifeStyle.Transient);
container.Register(Component.For<IHttpActionInvoker>().ImplementedBy<ApiControllerActionInvoker>().LifeStyle.Transient);
container.Register(Component.For<System.Web.Http.Metadata.ModelMetadataProvider>().ImplementedBy<System.Web.Http.Metadata.Providers.CachedDataAnnotationsModelMetadataProvider>().LifeStyle.Transient);
container.Register(Component.For<HttpConfiguration>().Instance(configuration));

//Register all api controllers
container.Register(AllTypes.FromAssembly(assemblyToRegister)
                            .BasedOn<IHttpController>()
                            .Configure(registration => registration.Named(registration.ServiceType.Name.ToLower().Replace("controller", "")).LifeStyle.Transient));

//Register WindsorHttpControllerFactory with Service resolver
GlobalConfiguration.Configuration.ServiceResolver.SetService(typeof(IHttpControllerFactory), container.Resolve<IHttpControllerFactory>());

I had to create my own implementation of an ILogger you could use a stub version like bellow.

public class MyLogger : System.Web.Http.Common.ILogger
{
    public void LogException(string category, TraceLevel level, Exception exception)
    {
        // Do some logging here eg. you could use log4net
    }

    public void Log(string category, TraceLevel level, Func<string> messageCallback)
    {
        // Do some logging here eg. you could use log4net
    }
}
Ottilie answered 15/3, 2012 at 14:17 Comment(0)
J
2

I also faced this issue two days ago and this post helped me. And don't forget to add WebAPI controllers in your Windsor bootstrap.

container.Register(AllTypes.FromThisAssembly().BasedOn<IHttpController>().LifestyleTransient());

UPDATE for ASP.NET MVC 4 RC: This useful post tells you how to use Windsor with WebAPI, it works like a charm.

Jingo answered 25/3, 2012 at 16:32 Comment(0)
P
1

Look at this post

I didn't switch yet to mvc 4 beta including web.api (I'm still using WCF web api Prev6), but what has been pointed out into the thread seems the way to go

Predacious answered 5/3, 2012 at 16:30 Comment(0)
L
0

I wrote a post about how to do this and hook it up to RavenDB which you might find useful here

Lexington answered 21/3, 2012 at 12:24 Comment(0)
D
0

I have managed to resolve from my windsor container by using GlobalConfiguration.Configuration.ServiceResolver.SetResolver as shown here.

Example Windsor Code

private void BootStrapWindsorContainer()
{
     _container = new WindsorContainer()
          .Install(FromAssembly.This());

     var controllerFactory = new WindsorControllerFactory(_container.Kernel);

     ControllerBuilder.Current.SetControllerFactory(controllerFactory);
     ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(_container));
     GlobalConfiguration.Configuration.ServiceResolver.SetResolver(
         t =>
             {
                 try
                {
                    return _container.Resolve(t);
                }
                catch
                {
                    return null;
                }
            },
            t =>
            {
                try
                {
                    return _container.ResolveAll(t).OfType<object>();
                }
                catch
                {
             return new List<object>();
         }
    });
}

Returning null when you cannot resolve types works and it seems like MVC will new up the dependencies.

Edit: Make sure you have an installer for IHttpController

Deron answered 21/4, 2012 at 0:12 Comment(1)
There is a potential memory leak problem with this method as I described in the post more info here. #4141360 also it is recommended to use the controller factory in their docs stw.castleproject.org/…Ottilie
M
0
public class WindsorHttpControllerFactory : IHttpControllerActivator
    {
        readonly IWindsorContainer _container;

        public WindsorHttpControllerFactory(IWindsorContainer container)
        {
            _container = container;
        }

        public IHttpController Create(HttpRequestMessage request,
                                      HttpControllerDescriptor controllerDescriptor,
                                      Type controllerType)
        {
            var controller = (IHttpController)_container.Resolve(controllerType);

            request.RegisterForDispose(new Release(() => _container.Release(controller)));
            return controller;
        }

        class Release : IDisposable
        {
            readonly Action _release;

            public Release(Action release)
            {
                _release = release;
            }

            public void Dispose()
            {
                _release();
            }
        }
    }
Misconstrue answered 20/8, 2015 at 12:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.