【发布时间】: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