Resolving HttpControllerContext with Castle Windsor
Asked Answered
A

2

28

In the ASP.NET Web API, HttpControllerContext instances provide a lot of information about the current environment, including the URI of the current request.

If a service relies on such information (e.g. the request URI), it should be possible to inject that information into the service.

This is pretty easy to do using Poor Man's DI: just implement a custom IHttpControllerActivator.

However, with Castle Windsor this suddenly becomes very difficult. Previously, I've described a very convoluted way to resolve this issue, but it hinges on the PerWebRequest lifestyle, and it turns out that this lifestyle doesn't work in self-hosting scenarios, because HttpContext.Current is empty.

So far, I've been able to make this work by passing the desired information as an inline argument to the Resolve method from a custom IHttpControllerActivator:

public IHttpController Create(
    HttpControllerContext controllerContext,
    Type controllerType)
{
    var baseUri = new Uri(
        controllerContext
            .Request
            .RequestUri
            .GetLeftPart(UriPartial.Authority));

    return (IHttpController)this.container.Resolve(
        controllerType,
        new { baseUri = baseUri });
}

However, by default, this only works if the immediately requested type relies on the argument (i.e. if the requested Controller itself depends on the baseUri). If the dependency on baseUri is buried deeper in the dependency hierarchy, it doesn't work by default, because inline arguments aren't propagated to deeper layers.

This behavior can be changed with a custom IDependencyResolver (a Castle Windsor IDependencyResolver, not an ASP.NET Web API IDependencyResolver):

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

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

Notice that true is being passed as the propagateInlineDependencies constructor argument instead of false, which is the default implementation.

In order to wire up a container instance with the InlineDependenciesPropagatingDependencyResolver class, it must be constructed in this way:

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

I'm wondering if this is the best solution to this problem, or if there's a better/simpler way?

Apotropaic answered 1/6, 2012 at 17:17 Comment(5)
Why do you want to do this? If it's for unit testing, I have banged my head against this same issue for a long time with no luck, and finally have gone to integration testing only with controllers that depend on using information in the context.Sclerophyll
The links in the question provide a rationale for doing this. blog.ploeh.dk/2012/04/17/…Apotropaic
@MarkSeemann Hi Mark, have you been able to come up with a better solution for this issue other than your blog post here: blog.ploeh.dk/2012/04/19/…Huntsville
@Xerxes No, but these days I exclusively do Pure DI, so the Pure DI approach is what I use these days. Here's the correct way to wire up Web API using Pure DI: blog.ploeh.dk/2012/09/28/…Apotropaic
@MarkSeemann thank you fro the link and your blog, its a treasure trove :)Huntsville
A
3

Just for the sake of completeness, an answer I got from Krzysztof Koźmic (the current maintainer of Castle Windsor) on Twitter indicated that the method outlined in the question is, indeed, the correct way of achieving this particular goal.

(However, I can't link to that tweet, since Krzysztof's twitter account is protected (tweets are not publicly visible.))

Apotropaic answered 15/10, 2012 at 21:11 Comment(0)
C
2

It seems to me that your InlineDependenciesPropagatingDependencyResolver is actually masking something fairly critical to the architecture of your application: that one or more of your components have dependencies that cannot be reliably resolved statically, from the container, or from dynamic context.

It violates the assumption most developers would make when passing inline dependencies to Resolve() (that they only get passed down one level of dependency resolution) and in certain scenarios could cause a dependency to incorrectly override some other configured service. (E.g. if you had another component many levels down which had a dependency of the same type and name). It could be a potential cause of bugs that would be very difficult to identify.

The issue at the heart of this is a difficult one for DI and really indicates that IoC is not really feasible (i.e. our dependency(ies) need to be 'pushed' and cannot be 'pulled' for us by the container). It seems to me there are two options:

1) rectify the problem that is preventing 'inversion'. i.e. wrap the HttpControllerContext/HttpContext, augment that wrapper to behave as required in a self-hosted scenario and have your components rely on that wrapper, rather than HttpControllerContext/HttpContext directly.

2) reflect the shortcomings of the environment you are working with (that it doesn't fully support 'inversion') and make the workaround to handle those shortcomings very explicit. In your scenario this would probably involve utilising a typed factory (interface) to instantiate the component requiring a baseUri in your IHttpControllerActivator.Create(). This would mean that if this component was further down the dependency hierarchy, you would need to explicitly build up your dependency hierarchy until you had your controller.

I would probably go for the second option simply because when conventions don't cut it I prefer to be as explicit as possible.

UPDATED Assuming we had a controller type A, which relied on component B, which in turn relied on the baseUri, the second option might look something like:

// Typed factories for components that have dependencies, which cannot be resolved statically
IBFactory bFactory; 
IAFactory aFactory;

public IHttpController Create(HttpControllerContext controllerContext, Type controllerType)
{
    if (controllerType == typeof(A))
    {
        // Special handling for controller where one or more dependencies
        // are only available via controllerContext.
        var baseUri = new Uri(controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Authority));
        B b = this.bFactory.Create(baseUri);
        return this.aFactory.Create(b);
    }
    // Default for all other controllers 
    return (IHttpController)this.container.Resolve(controllerType);
}

The key points are that this explicitly deals with the shortcomings of our environment, it binds the affected types specifically with the dependency overrides we provide imperatively and ensures that we are not accidentally overriding any other dependencies.

Cathleencathlene answered 15/7, 2012 at 23:48 Comment(9)
If we make the (rather reasonable) assumption that the extensibility points (e.g. IHttpControllerActivator) exposed by the ASP.NET Web API are given and can't be changed, then how do you propose to implement either of your two options?Apotropaic
AFAICT, this solution builds on the assumption that you know exactly where and how deep in the dependency graph the baseUri is required. That seems like a very brittle solution - e.g. if I refactor the API so that I push the baseUri a level down, this will start failing, and I'll have to manually fix this in my custom IHttpControllerActivator. As I'm currently using a completely convention-based wiring of dependencies, this doesn't seem like a good trade-off.Apotropaic
The suggestion is based on the same principles suggested in this answer: https://mcmap.net/q/504884/-castle-windsor-ioc-passing-constructor-parameters-to-child-components/246811 from Krzysztof Koźmic, where he explains that passing inline dependencies down the resolution pipeline, breaks an abstraction. For me, the fact that it will break if you refactor the API is desirable. The reality that this dependency can only be reliably made available in specific circumstances (i.e. via one execution path) should not be masked.Cathleencathlene
Which contract would be broken? This is pure infrastructure. What's happening here is that IHttpControllerActivator.Create is the only place we can pull data out of the current request context. We pass that data on to the resolution context. If any classes depend on this data, it's there. If not, no big deal. In both cases, nothing breaks.Apotropaic
Yes it will definitely work now. But you are overriding every dependency on a Uri baseUri constructor parameter, even if such an object was properly configured in the container. It creates the bizarre situation that if I create a new component that takes a Uri baseUri parameter in it's constructor and configure the container to provide it statically, your Resolve() call will override the statically configured object. However, if I named the parameter 'caseUri' your Resolve() call would not override it.Cathleencathlene
In other words the component author now has to be intimately familiar with the dependency injection mechanisms. If you're the only person that's ever going to be maintaining this code then it's probably not a problem - hopefully you'll remember the workaround that's been woven and all will be good. But if not, then it seems there's a potential for problems.Cathleencathlene
baseUri is resolved in one way, and caseUri is resolved differently... that's exactly where I want to be. This is called Convention over Configuration :)Apotropaic
OK I think I get where you're coming from now. Interesting discussion. Thanks. In another question somewhere IRC, Krzysztof was saying that if you're going to use propagateInlineDependencies you should do some filtering (i.e. not just do it for everything the container resolves)... So you don't have any concerns about passing inline dependencies down the dependency resolution pipeline? (Just trying to reconcile what might underpin two such apparently different opinions).Cathleencathlene
It's a piece of context that any service may decide to use if it needs it - or not. Do I have any concerns about it? I can't say that I have no concerns, but given the constraints posed by the Web API, this seems to best balance advantages and disadvantages. In the end, context is king, and I think Krzysztof had a different context in mind. In most other cases I would also agree with him.Apotropaic

© 2022 - 2024 — McMap. All rights reserved.