28

ASP.NET Web API中,HttpControllerContext 实例提供了很多关于当前环境的信息,包括当前请求的 URI。

如果服务依赖于此类信息(例如请求 URI),则应该可以将该信息注入到服务中。

使用 Poor Man 的 DI 很容易做到这一点:只需实现一个自定义 IHttpControllerActivator

然而,有了温莎城堡,这突然变得非常困难。之前,我已经描述了一种非常复杂的方法来解决这个问题,但是它取决于 PerWebRequest 生活方式,事实证明这种生活方式在自托管场景中不起作用,因为 HttpContext.Current 是空的。

到目前为止,我已经能够通过将所需信息作为内联参数从自定义 IHttpControllerActivator 传递给 Resolve 方法来完成这项工作:

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

但是,默认情况下,这仅在立即请求的类型依赖于参数时才有效(即,如果请求的 Controller 本身依赖于baseUri)。如果依赖关系在baseUri依赖层次结构中更深,则默认情况下它不起作用,因为内联参数不会传播到更深层。

可以使用自定义 IDependencyResolver(Castle Windsor IDependencyResolver,而不是 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);
    }
}

请注意,true它作为propagateInlineDependencies构造函数参数而不是false作为默认实现传递。

为了将容器实例与 InlineDependenciesPropagatingDependencyResolver 类连接起来,它必须以这种方式构造:

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

我想知道这是否是解决此问题的最佳方法,或者是否有更好/更简单的方法?

4

2 回答 2

3

为了完整起见,我在 Twitter 上从 Krzysztof Koźmic(Castle Windsor 的当前维护者)那里得到的回答表明,问题中概述的方法确实是实现这一特定目标的正确方法。

(但是,我无法链接到该推文,因为 Krzysztof 的推特帐户受到保护(推文不公开可见。))

于 2012-10-15T21:11:57.947 回答
2

在我看来,您的 InlineDependenciesPropagatingDependencyResolver 实际上掩盖了一些对您的应用程序架构相当重要的东西:您的一个或多个组件具有无法从容器或动态上下文中静态可靠地解析的依赖项。

它违反了大多数开发人员在将内联依赖项传递给 Resolve() 时所做的假设(它们只传递了一级依赖项解析),并且在某些情况下可能导致依赖项错误地覆盖某些其他配置的服务。(例如,如果您有另一个组件向下许多级别具有相同类型和名称的依赖项)。这可能是导致很难识别的错误的潜在原因。

这个问题的核心对于 DI 来说是一个难题,并且确实表明 IoC 并不是真正可行的(即我们的依赖项需要被“推送”并且不能被容器为我们“拉取”)。在我看来,有两种选择:

1)纠正阻止“反转”的问题。即包装 HttpControllerContext/HttpContext,扩充该包装器以在自托管场景中按要求运行,并让您的组件依赖该包装器,而不是直接依赖 HttpControllerContext/HttpContext。

2)反映您正在使用的环境的缺点(它不完全支持“反转”),并使解决这些缺点的解决方法非常明确。在您的场景中,这可能涉及使用类型化工厂(接口)来实例化baseUri您的IHttpControllerActivator.Create(). 这意味着如果这个组件在依赖层次结构中更靠后,你需要显式地建立你的依赖层次结构,直到你有你的控制器。

我可能会选择第二个选项,因为当约定不削减它时,我更喜欢尽可能明确。

更新 假设我们有一个控制器类型A,它依赖于组件B,而组件又依赖于 baseUri,第二个选项可能看起来像:

// 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);
}

关键点是,这明确地处理了我们环境的缺点,它将受影响的类型与我们强制提供的依赖覆盖特别绑定,并确保我们不会意外覆盖任何其他依赖。

于 2012-07-15T23:48:00.660 回答