【问题标题】:How to implement this mixed object Lifetime with Simple Injector如何使用 Simple Injector 实现这个混合对象的 Lifetime
【发布时间】:2016-07-04 17:45:08
【问题描述】:

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

为了带来一些上下文(如果有帮助),Consumer 类可能是来自 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 答案https://stackoverflow.com/a/29808487/625113 然而, 似乎一切都可以是瞬态的或作用域的,但不是某些特定的对象是作用域的,其余的都是瞬态的(这似乎很奇怪)。

更新 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,我会收到 Lifetime mismatch 异常。即

 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 不是范围内管理。

所以我的问题更具体地说是如何在 Simple Injector Scope 的上下文中实例化多个 MathHelper?

更新 2 - 更多背景和示例

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

就“范围”依赖解析而言,“范围”(在此应用程序中)被认为是在表单的生命周期内,因此一个 DbContext(如上面示例中的 SharedService)在 form/viewModel 所需的服务和大部分服务是每个范围的,其中一些注入的服务或帮助程序类需要作为瞬态注入。

这(对我而言)类似于 http://blog.ploeh.dk/2014/06/03/compile-time-lifetime-matching/ 中 Mark Seemann 的手动编码示例,其中他有一个按请求(单例范围)服务,该服务具有 注入其中的瞬态对象。

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

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

为了解决@Steve 和@Ric .net 的评论和回答,我可以看到最终可能会出现两个不同的服务使用另一个使用瞬态对象(存储状态)的共享服务和所谓的瞬态对象变成了 在“某些”服务的上下文中的单例作用域对象。例如

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 的用途,结果“可能”会出乎意料。

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

话虽如此,我的原始构图或 Mark Seemann 的示例是否可以在 Simple Injector 中注册并具有所需的生命周期,或者是否严格不可能通过设计而更好地手动组合对象 在可以进行进一步重构/强化之前,为需要这样做的实例绘制图表(或注入 Func,如@Ric .net 建议的那样)?

更新 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();

Simple Injector by design 没有,我相信我的第二个例子就是一个例子。

仅供参考:在我的实际案例中,对于对象图具有此功能的少数实例,我手动构建了图(只是为了切换到 Simple Injector),目的是重构这些潜在问题。

【问题讨论】:

  • 你的问题对我来说是一个很大的危险信号。您基本上是在说每个作用域实例都应该获得自己的 MathHelper 实例。这表明此 MathHelper 具有可变状态。一般来说,服务应该是不可变的。尝试更改 MathHelper 的设计。一旦它不可变,您的问题就会消失,因为它也可以注册为作用域。
  • '他有一个按请求(单例范围)服务,其中注入了瞬态对象。'对不起,我以前读过很多次这篇文章,我又读了一遍。马克没有将瞬态服务注入单例!这样做,即使使用纯 DI 也会导致强制依赖。这在文章中也有明确说明!
  • AFAIS 声明“他有一个 Per Request(单例范围)服务,其中注入了 Transient 对象”是不正确的。我没有看到 Mark 将任何瞬态注入到寿命更长的服务中,我无法想象他会这样做,因为 Mark 在另一篇博文中清楚地描述了 Captive Dependencies 是一个问题。
  • @Steven - 我将 BarController 误读为每个请求范围的服务。我已经相应地更新了我的问题并得出了一般性结论。如果我的结论/假设不正确,请纠正我。
  • @Ric .Net - 我将 BarController 误读为按请求范围的服务。我已经相应地更新了我的问题并得出了一般性结论。至于另一篇关于俘虏依赖的文章,直到我自己解决了一个场景,我才发现它们带来的潜在问题。

标签: c# dependency-injection simple-injector


【解决方案1】:

正如链接的SO question 中所解释的,Lifestyle Mismatch exception 的基本意思是,当您让一个对象依赖于另一个生命周期较短的对象时,您正在创建一个所谓的captive dependency

虽然您可能听起来很奇怪,但强制依赖是:

  1. 现实生活中的问题,如果不被发现,通常会导致非常奇怪的错误,这些错误很难调试
  2. 一个信号,正如 Steven 所指出的,服务有某种状态,这从来都不是一个好信号

如果MathHelper 具有可变状态,因此需要作为瞬态注入其他服务,则MathHelper 已成为某种“运行时数据”。还有injecting runtime data is an anti-pattern

这篇博文详细描述了运行时数据的问题以及解决方法。按照描述解决您的问题是有路可走的!

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

然而,还有其他几个选项,它们会起作用,但不是很好的设计 IMO。所以建议不要使用这些,而是​​要完整:

您可以注入 MathHelperProvider 或者更简单的注入 Func&lt;MathHelper&gt;,而不是将 MathHelper 作为瞬态注入,您可以将其注册为单例:

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

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

我想到的其他解决方案在设计上是如此丑陋,以至于在写完它们之后,我决定将它们排除在这个答案之外!

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

【讨论】:

  • RegisterSingleton Func 不起作用,因为 Simple Injector 仍将其视为生命周期不匹配(即 MathHelper 仍被视为瞬态)。
  • 是的,当然已经阅读了 Steven 的文章,尽管有时将简单示例与您面前的真实代码块联系起来需要一些时间 :)。一个基本示例可能是计算在该范围内可能在各种服务上调用关键函数的次数。话虽如此,将 MathHelper(以及类似的 DbContext/repositor)视为有状态/运行时数据对于重构它们很有用。谢谢。
  • @mips 很抱歉我(也许太迟了)反应迟钝,但将工厂注册为 Func 不应引发生活方式不匹配。我无法重现这个。如果您在注册时需要任何帮助,请更新您的问题并提出新问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多