1

假设我有一个需要静态属性的方法,并且我想创建单元测试,所以我将它包装在一个包装类中。让我们调用接口 IFoo 和具体类 Foo。

现在,如果从 MVC 视图中调用我的方法,如何将包装器实例放入该方法中?

显然,我可以在我的控制器构造函数中添加一个 IFoo 参数,在我的视图模型中添加一个 IFoo 属性,在我的方法中添加一个 IFoo 参数,然后将它传递到链中;控制器、视图模型、视图、扩展方法。这似乎让我无法接受。

那么有没有更清洁的方法来做到这一点?

我认为 DI Container 是要走的路。老实说,到目前为止我还不需要一个,我天真地假设我只需添加 Ninject,将具体类型绑定到接口,然后在我的方法中进行以下调用。

var dt = kernel.Get<IFoo>();

我认为这将帮助我避免上面提到的整个构造函数参数/属性跟踪。现在我知道我仍然需要从某个地方获取内核变量,但我想我记得看到一些关于使用线程/会话/请求范围调用它的东西。我以为我可以实例化同一个内核实例,而不管它在哪里被调用,但是当我查看它时,我发现这仅适用于内核正在调用的对象实例......而不是内核本身。

那么,有没有办法让一个 Foo 的实例进入方法而不通过一堆除了传递它什么都不做的对象传递它?

4

3 回答 3

1

首先,购买http://manning.com/seemann——你不会后悔的。

一般来说,“我在我的对象树中,给我 X”的问题可以通过以下方式之一解决:

  • 构造函数注入 - 任何需要 X 的对象,在构造时都需要它。请参阅 Ninject Wiki 中的注入模式
  • 服务位置 - 每个人都可以随时向中心母亲大喊大叫来获取东西 - 即将容器的引用保持在静态中并随意调用它 - 参见 ploeh 同名文章
  • 抽象工厂 - 你被注入一个Func<T>或一个 interface IXFactory { X CreateX(); }- 请参阅 Ninject.Extensions.Factory wiki
  • 方法注入 - 它在某处创建并根据需要传递它 - 请参阅 .NET 中的依赖注入一书

您期望容器的一些功能可以使任何地方的任何人都可以神奇地尖叫“我想要 X”,尤其是在从 DTO 调用的静态类中。请记住,容器不会进入并将您的 IL 拦截调用重写为 new。In 基本上可以应用一些装饰器、工厂和代理作为胶水,当一起做不起眼的布线工作时。

底线是处理这样的事情的适当方法是

  • 任何需要 DI 的东西都应该(并且可以)不是静态的,时期
  • 诸如 DTO 之类的短期对象实例不应该参与 DI(这并不是说即使在处理请求时也应该自信地组合对象图 - 只是您所连接的应该是服务,而不是对象)。请参阅为什么不使用 IoC 容器来解决实体/业务对象的依赖关系?
  • 大多数情况下,您最好通过方法注入和构造函数注入来明确依赖关系 - 这样它将您的需求推到表面,而不是通过服务位置创建带外通信老鼠巢

最后一点很关键——通过呈现真正的依赖关系并倾听它们,您最终会弄清楚时间服务、调度程序或格式化程序(或您的案例中缺少的任何更高级别的抽象)应该随着时间的推移而存在的位置。

我强烈建议在这里阅读Mark Seemann 的最佳答案,因为这些重要问题和主题中的大多数都在它们中得到了深入解决,而不像我的答案那样依赖过于简单的解释。当然,这本书本身是对您的时间和金钱的最佳综合利用!

编辑:(受到您对@tvonfosson 的评论的启发)Container 可以为聚会带来的一件关键事情(除了在给定范围内维护单个 X (请参阅 Ninject.Extensions.NamedScope wiki)是能够跳过传递的间接依赖项无缘无故地降低服务层次结构 - 即,如果您的控制器依赖于依赖于调度程序的服务并且调度程序需要一个时钟,则服务不需要谈论时钟,只是调度程序。

Containers 的关键是确保你不会走得太远,然后尝试滥用它们来获取应该只是代码的代码。

于 2013-04-24T23:27:56.483 回答
1

如果您的 ViewModel 或 View 也是由 Ninject 创建的,您应该能够将 IFoo 添加为构造函数依赖项并将其注入。如果您想直接访问Kernel并解析一个实例(这通常不是最好的主意),您可以注入一个IResolutionRoot并调用 Get 。

于 2013-04-21T12:00:11.353 回答
1

由于我不知道您的确切用例,我可能会建议也可能不合适。我有几种方法来处理这个问题,这取决于你在哪里测试。从扩展方法的角度来看,我认为您可以DateTime在扩展的单元测试中手动注入适当的值。如果它需要针对 测试一个值 DateTime.UtcNow那有点不同,但是您可以使用一个 nullable 来处理它,如果您没有在第二个参数中提供 on,则DateTime默认为该值。DateTime.UtcNow

   public static IHtmlString RegularExtension(this HtmlHelper helper, DateTime when)
   {
      ...
   }

   public static IHtmlString ComparisonExtension(this HtmlHelper helper, DateTime when, DateTime? now = null)
   {
       var nowDate = now ?? DateTime.UtcNow; // verified by inspection, tests use specified values
       ...
   }

或者,对于后者,如果您对 null 合并运算符感到不舒服,请使用两种方法。一个接受单个参数的公共参数和一个同时使用这两个参数的私有参数。使用反射测试您的私有方法,让公共方法简单地委托给私有方法。您也可以考虑使用需要两个参数的单一方法,并始终将两个值都构建到模型中。

从控制器的角度来看,如果您需要在当前时间使用一致的值,您可以将包装器注入控制器并使用包装器中的“未包装”值填充模型。这样,您可以测试控制器是否正确设置模型上的值,而不必将下游的所有内容都绑定到包装器。关键是您的所有代码都使用注入(和解包)的值,而不是DateTime.UtcNow直接调用。从这些类的测试的角度来看,他们不知道值来自哪里,只是它是从上游提供的。

   public FooController(IDateTimeWrapper timeWrapper)
   {
       var model = new FooModel { Now = timeWrapper.Unwrap(), ...  };

       ...

       return View(model);
   }
于 2013-04-20T21:38:02.037 回答