Castle Windsor ApiController Factory implementation for ASP.NET Web API

十年热恋 提交于 2019-11-29 02:07:10

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
    }
}

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.

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

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

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

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();
            }
        }
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!