Resolving HttpRequestMessage with Castle Windsor
Asked Answered
C

1

0

I've tried following advice from existing posts to make HttpRequestMessage available as a constructor dependency for services in Web API:

ASP Web Api - IoC - Resolve HttpRequestMessage

Resolving HttpControllerContext with Castle Windsor

This advice works fine if all the dependencies only have one constructor. But when a dependency has multiple constructors, dependency resolution fails.

Any ideas how to extend the idea to work with multiple constructors?

=======================

The existing approach is summarised as follows:

First you add the HttpRequestMessage as an additional named argument when resolving the controller in your IHttpControllerActivator:

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

Then you propagate this argument in the CreationContext:

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

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

This works fine when all the dependencies only have one constructor.

In my case, I have a hierarchy of dependencies:

  • The controller depends on IServiceA
  • ServiceA depends on IServiceB
  • ServiceB depends on IServiceC
  • ServiceC depends on HttpRequestMessage

Where ServiceC looks like this:

public class ServiceC: IServiceC
{
    private readonly HttpRequestMessage request;

    public ServiceC(HttpRequestMessage request)
    {
        this.request = request;
    }

And ServiceB has two constructors:

public class ServiceB: IServiceB
{
    public ServiceB(string paramForTests)
    {
        // Do stuff
    }

    public ServiceB(IServiceC serviceC)
    {
        // Do stuff
    }

But then Windsor fails to resolve ServiceC.

The problem seems to be in the SelectEligibleConstructor logic of DefaultComponentActivator. It calls into the CanResolve method in DefaultDependencyResolver which eventually ends up at:

protected virtual bool CanResolveFromKernel(CreationContext context, ComponentModel model, DependencyModel dependency)
{
    if (dependency.ReferencedComponentName != null)
    {
        // User wants to override
        return HasComponentInValidState(dependency.ReferencedComponentName, dependency, context);
    }
    if (dependency.Parameter != null)
    {
        return true;
    }
    if (typeof(IKernel).IsAssignableFrom(dependency.TargetItemType))
    {
        return true;
    }

    if (dependency.TargetItemType.IsPrimitiveType())
    {
        return false;
    }

    return HasAnyComponentInValidState(dependency.TargetItemType, dependency, context);
}

And then HasAnyComponentInValidState just looks at whether ServiceC has already been resolved, it doesn't actually check whether it can be resolved.

If there is only one constructor, then the code calls the Resolve methods, which correctly recursively resolve the dependencies, and ServiceC is available ok.

I don't want to limit my services to only having one constructor (or use the [DoNotSelect] attribute to only leave one for Castle to look at).

Any ideas how to inject arguments as I have done, and still have it work with multiple constructors?

Carolecarolee answered 14/6, 2018 at 10:48 Comment(0)
C
0

I found an alternative way of resolving the HttpRequestMessage parameter, which now works.

Just use a factory method to spin up the service with the dependency on HttpRequestMessage, where you manually extract the required "request" argument from the context which has been passed down from the controller activator:

public class Installer : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component
                .For<IServiceC>()
                .UsingFactoryMethod((kernel, context) => {
                    var request = (HttpRequestMessage)context.AdditionalArguments["request"];
                    var serviceC = new ServiceC(request);
                    return serviceC;
                })
                .LifestyleTransient()
        );
    }
}
Carolecarolee answered 21/6, 2018 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.