如何使用 Simple Injector 实现这个混合对象 Lifetime

How to implement this mixed object Lifetime with Simple Injector

我想知道如何注册 classes 并设置一个 Simple Injector 容器以按以下方式实例化 classes。即从手动 DI 到让下面的 Consumer class 注入 CompositeService 并按如下方式设置对象图和生命周期:

为了提供一些上下文(如果有帮助的话),Consumer class 可能是来自 WPF 应用程序的 ViewModel,它在请求 View 时被实例化。

public class Consumer
{
   public Consumer()
   {
      var sharedSvc = new SharedService();
      var productSvc = new ProductService(sharedSvc, new MathHelper());
      var compositeSvc = new CompositeService(sharedSvc, productSvc, new MathHelper());
      compositeSvc.Process();
   }
}

其中: MyContext 应该在调用范围内共享。 ProductService 和 CompositeService 可以是瞬态的或在范围内共享。 MathHelper 必须是瞬态的。

问:如何使用 Simple Injector 实现上述目标?

或更具体地说: 如何在 Simple Injector Scope 的上下文中实例化多个 MathHelper?

我已阅读 http://simpleinjector.readthedocs.org/en/latest/lifetimes.html 并阅读并遵循了 SO 答案 但是, 似乎一切都可以是瞬态的或范围内的,但某些特定对象不是范围内的,其余的是瞬态的(这看起来很奇怪)。

更新 1

以下使用 Simple Injector 将实现 SharedService 结果,但如果我希望 ProductService 和 CompositeService 也具有范围内的生命周期,它将无法工作:

 cont.RegisterLifetimeScope<SharedService>();
 cont.Register<MathHelper>();
 cont.Register<ProductService>();
 cont.Register<CompositeService>();

 using (cont.BeginLifetimeScope())
 {
    var compositeSvc = cont.GetInstance<CompositeService>();
    compositeSvc.Process();
 }

如果我将 ProductService 或 CompositeService 中的一个或两个注册为 RegisterLifetimeScope,我会收到一个生命周期不匹配异常。即

 cont.RegisterLifetimeScope<SharedService>();
 cont.Register<MathHelper>();
 cont.RegisterLifetimeScope<ProductService>();
 cont.Register<CompositeService>(); // or cont.RegisterLifetimeScope<CompositeService>();

 using (cont.BeginLifetimeScope())
 {
    var compositeSvc = cont.GetInstance<CompositeService>(); // Exception thrown
    compositeSvc.Process();
 }

抛出导致此页面的异常:https://simpleinjector.readthedocs.org/en/latest/LifestyleMismatches.html

我可以理解,关于 Singleton 应该依赖于 Transient,并且可以推断出一种理解,在这种情况下也可以这样说,Simple Injector 警告说 Scoped 不能依赖于 Transient,因为 Transient 是'范围内管理。

所以我的问题更具体地说是如何在简单注入器作用域的上下文中实例化多个 MathHelper?

更新 2 - 更多背景和示例

简要背景 - 出现这种情况是因为我有一个已有 4 年历史的基于 WPF 的 2 层应用程序,目前正在使用 Ninject,它具有臃肿的混合服务架构@Steven 描述的 在他的博客系列中(即服务已成为混合的、半相关的、命令和查询的混搭)。这些服务中的大多数都可以很好地分离出来 ICommandHandler/IQueryHandler 架构...但是你不能一夜之间做事,所以第一个破解是从 Ninject 转换为 SimpleInjector(是的,我知道 Ninject 可以在这方面做同样的事情建筑学 但还有其他原因转向 SimpleInjector)。

就 "scoping" 依赖项解析而言,"scope"(在此应用程序中)被认为是表单的生命周期,因此一个 DbContext(如上例中的 SharedService)是共享的 form/viewModel 需要的服务和大多数服务是每个范围的,一些注入的服务或助手 classes 需要作为瞬态注入。

这(对我来说)类似于来自 http://blog.ploeh.dk/2014/06/03/compile-time-lifetime-matching/ 的 Mark Seemann 的手写示例,他有一个 Per Request(单例范围)服务 瞬态对象注入其中。

编辑: 我误读了 Mark Seemann 的示例,在阅读代码时就好像 BarController 是一项服务。因此,虽然 BarController 对象组成相同,但生命周期不同。那就是说 SomeThreadUnsafeService 可以很容易地向其中注入一个新的 SomeServiceThatMustBeTransient,但是,我的观点是正确的,他的示例没有这样做。

因此我想知道如何在 Simple Injector 中但在 web reqeusts 的上下文之外进行 Mark Seemann 的对象组合(我的假设是 Simple Injector 的 每个 Web 请求范围本质上是一种特定类型的生命周期范围。

为了解决@Steve 和@Ric .net 的评论和回答,我可以看到有可能以两种不同的服务使用另一种使用瞬态对象(存储状态)的共享服务的情况结束而假设的瞬态对象变成了 这些服务的 "some" 上下文中的 Singleton Scoped 对象。例如

public class SingletonScopedService1
{
   private readonly TransientX _transientA;

   public SingletonScopedService1(TransientX transientA)
   {
      _transientA = transientA;
   }

   public void PokeTransient()
   {
      _transientA.Poke();
   }
}

public class SingletonScopedService2 
{
   private readonly SingletonScopedService1 _service1;
   private readonly TransientX _transientB;

   public SingletonScopedService2(SingletonScopedService1 service1, TransientX transientB) 
   {
      _service1 = service1;
      _transientB = transientB;
   }

   public void GoFishing()
   {
      _service1.PokeTransient();
      // This TransientX instance isn't affected
      _transientB.Poke();
   }
}

public class SingletonService3 
{
   private readonly SingletonScopedService1 _service1;

   public SingletonService3(SingletonScopedService1 service1)
   { 
      _service1 = service1; 
   }

   public void DoSomething()
   {
      _service1.PokeTransient();
   }
}

如果在 SingletonScopedService3 上调用 DoSomething() 并在 SingletonScopedService2 上调用 GoFishing()(并假设 TransientX 保持状态),则根据 TransientX 的目的,结果 "may" 是意外的。

所以我很高兴接受这一点,因为应用程序按预期运行(但也接受当前的组合是脆弱的)。

话虽如此,我的原始组合或 Mark Seemann 的示例是否可以在 Simple Injector 中注册所需的生命周期,或者是否完全不可能通过设计和更好地手动组合对象 图形(或按照@Ric .net 的建议注入 Func)用于需要此操作的实例,直到进一步 refactoring/hardening 可以完成?

更新 3 - 结论

虽然 Ninject 允许您注册我的原创作品,例如:

     var kernel = new StandardKernel();
     kernel.Bind<SharedService>().ToSelf().InCallScope();
     kernel.Bind<MathHelper>().ToSelf();
     kernel.Bind<ProductService>().ToSelf().InCallScope();
     kernel.Bind<CompositeService>().ToSelf().InCallScope();

设计简单的注入器不会,我相信我的第二个例子就是为什么的例子。

仅供参考:在我的真实案例中,对于对象图有这个的少数情况,我手动构建了图(只是为了切换到简单注入器),目的是重构这些潜在的问题.

如链接 what the Lifestyle Mismatch exception is basically saying is that you're creating a so called captive dependency 中所述,当您让一个对象依赖于另一个生命周期较短的对象时。

虽然这对你来说可能听起来很奇怪,但俘虏依赖是:

  1. 一个现实生活中的问题,如果未被发现,通常会导致非常难以调试的非常奇怪的错误
  2. 一个标志,如 Steven 所指出的,表明服务处于某种状态,这绝不是一个好兆头

如果 MathHelper 具有可变状态,因此需要作为瞬态注入其他服务,MathHelper 已成为某种 'runtime data'。还有 injecting runtime data is an anti-pattern.

这篇博文详细描述了运行时数据的问题是什么以及如何解决这个问题。按照描述解决您的问题是可行的!

因为你没有指定 MathHelper 的实现细节,所以很难给你一些建议,你应该如何重构 MathHelper。所以我能给你的唯一建议是,让运行时数据或 'state' 作为消息在系统中流动。您可以阅读有关基于消息的设计 here and here

然而,还有其他几个选项可以工作,但在我看来并不是好的设计。所以建议不要用这些,要完整:

而不是注入 MathHelper 作为一个瞬态你可以注入一个 MathHelperProvider 或者更简单的注入一个 Func<MathHelper> 你可以注册为单例:

container.RegisterSingleton<Func<MathHelper>>(() => container.GetInstance<MathHelper>());

请注意,通过注册委托,您将使容器变为盲目的。它将无法再警告您这部分对象图的配置错误。

我想到的其他解决方案的设计非常丑陋,在编写它们之后,我决定将它们从这个答案中删除!

如果您要添加一些关于为什么 MathHelper 需要瞬态的详细信息,我可以给您一些建议,您可以在哪些地方进行调整以使其具有作用域甚至更好:单例。