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

时间:2016-03-18 04:56:31

标签: c# dependency-injection simple-injector

我想知道如何注册类并设置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不匹配异常。即

 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 isn& #39;在范围内进行管理。

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

更新2 - 更多背景和示例

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

至于&#34;范围&#34;依赖性解决方案,&#34;范围&#34; (在这个应用程序中)被认为是一个表单的生命,所以一个DbContext(如上例中的SharedService)是共享的 表单/ viewModel需要的服务和服务的MOST是每个作用域,一些注入的服务或辅助类需要作为Transient注入。

这(对我而言)类似于Mark Seemann的http://blog.ploeh.dk/2014/06/03/compile-time-lifetime-matching/手动编码示例,其中有一个Per Request(单一作用域)服务, 注入瞬态物体。

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

因此我想知道如何在简单注入器中完成Mark Seemann所做的对象组合,但是在web reqeusts的背景之外(我的假设是Simple Injector&#39; s 每个Web请求范围本质上是特定类型的Lifetime范围。)

为了解决@Steve和@Ric .net的评论和回答,我可以看到有可能最终出现2个不同服务使用另一个使用瞬态对象的共享服务的场景(存储状态)和所谓的瞬态对象成为一个 Singleton Scoped对象在&#34; some&#34;那些服务。例如

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维持状态),则结果&#34;可能&#34;根据TransientX的目的而出乎意料。

所以我很高兴接受这个,因为申请按预期运作(但也接受目前的成分很脆弱)。

话虽如此,我的原创作品或Mark Seemann的例子可以在Simple Injector中注册所需的生命周期,还是严格来说不可能通过设计,更好地手动编写对象 对于需要这样做的实例,可以使用图形(或者为@Ric .net建议注入一个Func),直到可以进行进一步的重构/加固为止?

更新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),意图重构这些潜在的问题

1 个答案:

答案 0 :(得分:3)

正如链接的SO question中所解释的那样Lifestyle Mismatch exception基本上说的是,当你让一个对象依赖于另一个具有另一个对象时,你正在创建一个所谓的captive dependency更短的生活方式。

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

  1. 一个现实生活中的问题,如果未被发现,通常会导致非常奇怪的错误,这些错误很难调试
  2. 史蒂文所指出的一个标志,即该服务具有某种状态,这绝不是一个好兆头
  3. 如果MathHelper具有可变状态,因此需要在其他服务中注入瞬态,MathHelper已成为某种运行时数据&#39;。并injecting runtime data is an anti-pattern

    此博客文章详细描述了运行时数据的问题以及如何解决此问题。如上所述解决您的问题还有很长的路要走!

    由于您没有指定MathHelper的实施细节,因此很难向您提供有关如何重构MathHelper的建议。因此,我能给你的唯一建议是,让运行时数据或者说“#”;作为消息流过系统。您可以阅读有关基于邮件的设计herehere

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

    不是将MathHelper作为瞬态注入,而是注入MathHelperProvider甚至更简单的注入Func<MathHelper>,您可以注册为单身:

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

    请注意,通过注册委托,您将使容器失明。它不再能够警告您对象图的这一部分中的错误配置。

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

    如果你要添加一些关于为什么MathHelper需要短暂的细节,我可以给你一些建议,你可以做一些调整,以使其成为范围甚至更好:单身。