ASP Web Api - IoC - Resolve HttpRequestMessage
Asked Answered
H

2

10

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?

Hydrastis answered 19/10, 2012 at 15:39 Comment(6)
The post linked to above describes how to use Castle Windsor with a preview of the Web API. Since there were breaking changes between the preview and the RTM version, the approach described there no longer works. Please refer to blog.ploeh.dk/2012/10/03/… for a description of how to make DI work with Castle Windsor in Web API RTM.Jewry
thanks - yes - I have followed that. I just don't get how to do the registration for HttpRequestMessage that hyprlinkr needs. can you point me in the right direction?Hydrastis
Like this: https://mcmap.net/q/494796/-resolving-httpcontrollercontext-with-castle-windsor/126014Jewry
thanks once again - I now get an error message! so some progress. "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.". I have updated the question above with the code I am now using. Can you see anything obviously wrong? It sounds like it thinks the controller is registered as a Singleton, but it is definitely PerWebRequest. Any ideas?Hydrastis
You can't use PerWebRequest with the Web API. PerWebRequest relies on the Ambient Context HttpContext.Current, and that is null in the web API. Try using Transient, or write your own custom Castel Windsor lifestyle - my book explains how to do that: affiliate.manning.com/idevaffiliate.php?id=1150_236Jewry
Same error with Transient, unfortunately. I assume you must have managed this given you wrote Hyprlinkr? The sample in the github repo is very basic. do you have a more sophisticated example implementation that uses Castle?Hydrastis
J
13

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());
Jewry answered 23/10, 2012 at 0:6 Comment(9)
thanks - do you need to replace the IHttpeControllerActivator? GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(container)); If I don't do that, I get an error that my controller doesn't have a default constructor - so it's obviously not being constructed by castle. If I do add the line above then I get the same error as before. i.e. Cannot reuse an 'ApiController' instance. Can you show me what you controller registration looks like? I have this : container.Register(Component.For<ValuesController>().LifestyleTransient());Hydrastis
Please refer to blog.ploeh.dk/2012/10/03/… for the basics. You don't need to (and can't) register HttpRequestMessage exactly because there's a new instance for each invocation of IHttpControllerActivator.Create. The registration for ValuesController looks fine... From the information currently available, I can't tell what's wrong.Jewry
Yeah, it’s very odd. Maybe I have a different version of the web api to you? Is there any way to tell? Also – can you confirm if your version replaces the default IHttpControllerActivator i.e config.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(container));Hydrastis
I have uninstalled and reinstalled the MVC4 package, to make sure I'm on the latest version. And tried in a brand new project with the exact code above and still get the same error. I'm at a loss to explain what is wrong. Very frustrating....Hydrastis
The lifetime issue you are experiencing has nothing to do with Hyprlinkr. Try to isolate the issue to just Castle Windsor and a single ApiController. If you can't figure out what's wrong, try posting your code in a new question.Jewry
Sure - that's what I've been trying to do. In a new project with only the default "values" apicontroller. and the code verbatim from above. Still get the same errorHydrastis
Have added some more info to the question above. I believe it is happening because of the naming used in the anonymous object that specifies the arguments to pass along when newing up a controller.Hydrastis
Fantastic - I'd just recompiled hyprlinkr to have it's ctor arg called httpRequest. This is obviously a much preferable solution. Thanks again and great work on Hyprlinkr (i've also added your book to my reading list)Hydrastis
This worked for me, except when child-dependencies have multiple constructors. Have posted a further question on this at #50856199Gehring
E
0

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

Equal answered 20/10, 2012 at 6:5 Comment(2)
I'm more interested in finding out how to get HttpRequestMessage resolved. In either approach...Hydrastis
the Hyprlinkr library has a constructor dependency on it that I need to satisfyHydrastis

© 2022 - 2024 — McMap. All rights reserved.