使用 Castle Windsor 解析 HttpControllerContext

Posted

技术标签:

【中文标题】使用 Castle Windsor 解析 HttpControllerContext【英文标题】:Resolving HttpControllerContext with Castle Windsor 【发布时间】:2012-06-06 22:56:23 【问题描述】:

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

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

使用Poor Man's DI 很容易做到这一点:只需implement a custom IHttpControllerActivator。

但是,有了温莎城堡,这突然变得非常困难。之前I've described a very convoluted way 解决了这个问题,但是它依赖于 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 );

但是,默认情况下,这仅在立即请求的类型依赖于参数时才有效(即,如果请求的控制器本身依赖于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());

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

【问题讨论】:

为什么要这样做?如果是用于单元测试,那么我很长一段时间都没有遇到同样的问题,最后只对依赖于在上下文中使用信息的控制器进行了集成测试。 问题中的链接提供了这样做的理由。 blog.ploeh.dk/2012/04/17/… @MarkSeemann 嗨,马克,除了您在此处的博客文章之外,您是否能够为这个问题提出更好的解决方案:blog.ploeh.dk/2012/04/19/… @Xerxes 不,但这些天我专门做Pure DI,所以我现在使用的是纯 DI 方法。以下是使用 Pure DI 连接 Web API 的正确方法:blog.ploeh.dk/2012/09/28/… @MarkSeemann 谢谢你的链接和你的博客,它是一个宝库:) 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

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

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

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

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

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

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

更新 假设我们有一个控制器类型 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);

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

【讨论】:

如果我们做出(相当合理的)假设,即 ASP.NET Web API 公开的扩展点(例如 IHttpControllerActivator)是给定的并且无法更改,那么您建议如何实现你有两个选择吗? AFAICT,此解决方案建立在您确切知道依赖关系图中需要 baseUri 的位置和深度的假设之上。这似乎是一个非常脆弱的解决方案 - 例如如果我重构 API 以便将 baseUri 向下推,这将开始失败,我将不得不在我的自定义 IHttpControllerActivator 中手动修复此问题。由于我目前使用的是完全基于约定的依赖关系,这似乎不是一个好的权衡。 该建议基于此答案中建议的相同原则:来自 Krzysztof Koźmic 的***.com/q/3904951/246811,他在其中解释说,将内联依赖项传递到解析管道会破坏抽象。对我来说,如果你重构 API,它就会崩溃的事实是可取的。不应掩盖这种依赖关系只能在特定情况下(即通过一个执行路径)可靠地提供的现实。 哪个合同会被破坏?这是纯粹的基础设施。这里发生的是 IHttpControllerActivator.Create 是我们可以从当前请求上下文中提取数据的唯一地方。我们将该数据传递给解析上下文。如果任何类依赖于这些数据,它就在那里。如果没有,没什么大不了的。在这两种情况下,都没有中断。 是的,它现在肯定会起作用。但是您将覆盖对 Uri baseUri 构造函数参数的每个依赖项,即使在容器中正确配置了这样的对象也是如此。它产生了一种奇怪的情况,如果我在其构造函数中创建一个采用Uri baseUri 参数的新组件并将容器配置为静态提供它,那么您的 Resolve() 调用将覆盖静态配置的对象。但是,如果我将参数命名为“caseUri”,您的 Resolve() 调用将不会覆盖它。

以上是关于使用 Castle Windsor 解析 HttpControllerContext的主要内容,如果未能解决你的问题,请参考以下文章

Castle Windsor 在不创建实例的情况下解析服务实现类型

对Castle Windsor的Resolve方法的解析时new对象的探讨

使用 PerWebRequest 生活方式测试 Castle Windsor 组件

Castle.Windsor依赖注入的高级应用_Castle.Windsor.3.1.0

如何在使用反射加载的程序集中使用 Castle.Windsor

依赖注入 Castle.Windsor高级应用