ASP Web Api - IoC - Resolve HttpRequestMessage

2019-02-06 15:51发布

问题:

I am trying to set up Castle Windsor with ASP.NET WebAPI.

I am also using the Hyprlinkr package (https://github.com/ploeh/Hyprlinkr) and so need an instance of HttpRequestMessage injected in to one of the dependencies of my controller.

I am following this article by Mark Seemann - http://blog.ploeh.dk/2012/04/19/WiringHttpControllerContextWithCastleWindsor.aspx , but I am finding that although the API runs, when I make a call to it, the request just hangs. No error message. It’s as if it’s in an infinite loop. It’s hanging on the call to Resolve in my Custom ControllerActivator

I am thinking I have some of my Castle registrations wrong. If I remove the ones mentioned in the article above then I can successfully make a call to the API (albeit without the dependacies I need getting resolved)

Any ideas?

Code is Below

//Global.asax
public class WebApiApplication : HttpApplication
{
    private readonly IWindsorContainer container;

    public WebApiApplication()
    {
        container = 
            new WindsorContainer(
                new DefaultKernel(
                    new InlineDependenciesPropagatingDependencyResolver(), 
                    new DefaultProxyFactory()), 
                new DefaultComponentInstaller());

        container.Install(new DependencyInstaller());
    }

    protected void Application_Start()
    {        
        GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(this.container));
    }

// installer
public class DependencyInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.AddFacility<TypedFactoryFacility>();

        container.Register(
            Component.For<ValuesController>()
                .Named("ValuesController")
                .LifeStyle.PerWebRequest,

            Component.For<IResourceLinker>()
                .ImplementedBy<RouteLinker>()
                .LifeStyle.PerWebRequest,

            Component.For<IResourceModelBuilder>()
                .ImplementedBy<ResourceModelBuilder>()
                .LifeStyle.PerWebRequest,

                Component.For<HttpRequestMessage>()
                .Named("HttpRequestMessage")
                .LifeStyle.PerWebRequest
            );
    }
}

//Activator

public class WindsorCompositionRoot : IHttpControllerActivator
{
    private readonly IWindsorContainer container;

    public WindsorCompositionRoot(IWindsorContainer container)
    {
        this.container = container;
    }

    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller = (IHttpController)this.container.Resolve(controllerType, new { request = request });

        request.RegisterForDispose(
            new Release(
                () => this.container.Release(controller)));

        return controller;
    }

// DependencyResolver   
public class InlineDependenciesPropagatingDependencyResolver : DefaultDependencyResolver
{
    protected override CreationContext RebuildContextForParameter(CreationContext current, Type parameterType)
    {
        if (parameterType.ContainsGenericParameters)
        {
            return current;
        }

        return new CreationContext(parameterType, current, true);
    }
}

EDIT*********** ADDITIONAL INFO****************

So I set up a scenario where the controller just takes a HttpRequestMessage as a ctor argument and found :

This works:

//controller
public class ValuesController : ApiController
    {
        private readonly HttpRequestMessage _httpReq;

        public ValuesController(HttpRequestMessage httpReq)
        {
            _httpReq = httpReq;
        }
//IHttpControllerActivator
public IHttpController Create(
            HttpRequestMessage httpRequest,
            HttpControllerDescriptor controllerDescriptor,
            Type controllerType)
        {

            var controller = (IHttpController)this.container.Resolve(
                controllerType, new { httpReq = httpRequest });

            return controller;

However, this Doesn't.

//controller
public class ValuesController : ApiController
    {
        private readonly HttpRequestMessage _httpReq;

        public ValuesController(HttpRequestMessage request)
        {
            _httpReq = request;
        }

//IHttpControllerActivator
public IHttpController Create(
            HttpRequestMessage request,
            HttpControllerDescriptor controllerDescriptor,
            Type controllerType)
        {

            var controller = (IHttpController)this.container.Resolve(
                controllerType, new { request = request });

            return controller;

i.e. when the anon object has a property called "request" and the controller ctor arg is called "request". It is somehow making the controller think it's request property is null. Which is what causes the error I see:

Cannot reuse an 'ApiController' instance. 'ApiController' has to be constructed per incoming message. Check your custom 'IHttpControllerActivator' and make sure that it will not manufacture the same instance.

at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken) at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

have a read of this How can I enrich object composition in StructureMap without invoking setter injection?

It explains a similar scenario.

Of course, hyprlinkr has it's ctor arg for HttpRequestMessage called "request", so I do need to specify the anon object with that property name.

Any ideas?

回答1:

Here's a Composition Root that works for me:

public class WindsorCompositionRoot : IHttpControllerActivator
{
    private readonly IWindsorContainer container;

    public WindsorCompositionRoot(IWindsorContainer container)
    {
        this.container = container;
    }

    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller = (IHttpController)this.container.Resolve(
            controllerType,
            new
            {
                request = request
            });

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

    private class Release : IDisposable
    {
        private readonly Action release;

        public Release(Action release)
        {
            this.release = release;
        }

        public void Dispose()
        {
            this.release();
        }
    }
}

Here's how I create the container:

this.container =
    new WindsorContainer(
        new DefaultKernel(
            new InlineDependenciesPropagatingDependencyResolver(),
            new DefaultProxyFactory()),
        new DefaultComponentInstaller())
        .Install(new MyWindsorInstaller());

and here's the InlineDependenciesPropagatingDependencyResolver:

public class InlineDependenciesPropagatingDependencyResolver : 
    DefaultDependencyResolver
{
    protected override CreationContext RebuildContextForParameter(
        CreationContext current,
        Type parameterType)
    {
        if (parameterType.ContainsGenericParameters)
        {
            return current;
        }

        return new CreationContext(parameterType, current, true);
    }
}

Finally, here's how I register RouteLinker:

container.Register(Component
    .For<RouteLinker, IResourceLinker>()
    .LifestyleTransient());

One thing to be aware of is that the ApiController base class has a public property named Request of the HttpRequestMessage type. As explained in section 10.4.3 of my book Windsor will attempt to assign a value to each writeable property if it has a matching component - and that match is case-insensitive.

When you pass an HttpRequestMessage named request to the Resolve method, this is exactly what happens, so you need to tell Castle Windsor that it should forego Property Injection for ApiControllers. Here's how I don that in a convention-based registration:

container.Register(Classes
    .FromThisAssembly()
    .BasedOn<IHttpController>()
    .ConfigureFor<ApiController>(c => c.Properties(pi => false))
    .LifestyleTransient());


回答2:

Why not to use already built-in mechanism in ASP.NET Web API - Dependency resolver

http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver

In project WebApiContrib there is CastleWindsor resolver implementation, but as I've seen with Dependency resolver

https://github.com/WebApiContrib/WebApiContrib.IoC.CastleWindsor

And as Mark said in comment - one of the ways to implement IHttpControllerActivator

http://blog.ploeh.dk/2012/10/03/DependencyInjectionInASPNETWebAPIWithCastleWindsor.aspx