Jon*_*ois 5 c# dependency-injection decorator simple-injector
虽然我已经使用Ninject很长一段时间,但我使用Simple Injector是全新的,所以我对DI很满意.吸引我想要使用Simple Injector的一件事是装饰器的易用性.
我已经能够在所有正常情况下成功使用带有Simple Injector的装饰器,在请求服务时解析依赖关系.但是,我很难弄清楚是否有办法在必须使用运行时值构造服务的情况下应用我的装饰器.
在Ninject中,我可以传递一个请求,ConstructorArgument该kernel.Get<IService>请求可以从N个装饰器的链中继承到"真正的"实现类.我无法想出使用Simple Injector复制它的方法.
我在下面放了一些非常基本的代码来说明.我想在现实世界中想要做的是将一个IMyClassFactory实例传递给我的应用程序中的其他类.那些其他类可以使用它来创建IMyClass使用IRuntimeValue它们提供的实例.在IMyClass他们从GOT实例IMyClassFactory会自动注册的装修装饰.
我知道我可以手动将我的装饰器应用到我IMyClassFactory或Func<IMyClass>我想出的任何装饰中,但我希望它能"正常工作".
我一直四处寻找并试图抽象出MyClass构造,但我无法弄清楚如何使用IRuntimeValue构造函数参数来解决它并进行修饰.
我忽略了一个明显的解决方案吗
using System;
using SimpleInjector;
using SimpleInjector.Extensions;
public class MyApp
{
[STAThread]
public static void Main()
{
var container = new Container();
container.Register<IMyClassFactory, MyClassFactory>();
container.RegisterDecorator(typeof (IMyClass), typeof (MyClassDecorator));
container.Register<Func<IRuntimeValue, IMyClass>>(
() => r => container.GetInstance<IMyClassFactory>().Create(r));
container.Register<IMyClass>(() => ?????)); // Don't know what to do
container.GetInstance<IMyClass>(); // Expect to get decorated class
}
}
public interface IRuntimeValue
{
}
public interface IMyClass
{
IRuntimeValue RuntimeValue { get; }
}
public interface IMyClassFactory
{
IMyClass Create(IRuntimeValue runtimeValue);
}
public class MyClassFactory : IMyClassFactory
{
public IMyClass Create(IRuntimeValue runtimeValue)
{
return new MyClass(runtimeValue);
}
}
public class MyClass : IMyClass
{
private readonly IRuntimeValue _runtimeValue;
public MyClass(IRuntimeValue runtimeValue)
{
_runtimeValue = runtimeValue;
}
public IRuntimeValue RuntimeValue
{
get
{
return _runtimeValue;
}
}
}
public class MyClassDecorator : IMyClass
{
private readonly IMyClass _inner;
public MyClassDecorator(IMyClass inner)
{
_inner = inner;
}
public IRuntimeValue RuntimeValue
{
get
{
return _inner.RuntimeValue;
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑1:
好的,感谢史蒂文的出色答案.它给了我一些想法.
也许是为了让它更具体一点(虽然不是我的情况,更"经典").假设我ICustomer在运行时通过读取数据库或从磁盘或其他东西反序列化来创建.所以我想这可以被认为是一个"新手"引用史蒂文链接的一篇文章.我想创建一个实例,ICustomerViewModel以便我可以显示和操作我的ICustomer.我的具体CustomerViewModel类ICustomer在其构造函数中引入了另一个可由容器解析的依赖项.
所以我有一个定义了返回ICustomerViewModelFactory的.Create(ICustomer customer)方法ICustomerViewModel.在我提出这个问题之前,我总能得到这个工作,因为在我的实现中ICustomerViewModelFactory我可以这样做(工厂在组合根中实现):
return new CustomerViewModel(customer, container.GetInstance<IDependency>());
Run Code Online (Sandbox Code Playgroud)
我的问题是,我希望我ICustomerViewModel的容器被容器装饰,然后将其翻新.现在我知道如何解决这个限制.
所以我想我的后续问题是:我的设计首先是错误的吗?我真的觉得ICustomer应该将其传递给构造函数,CustomerViewModel因为它表明了它是必需的,得到验证的等等.我不想在事后添加它.
Simple Injector明确缺乏对通过该GetInstance方法传递运行时值的支持.原因是在构造对象图时不应使用运行时值.换句话说,注入的构造函数不应该依赖于运行时值.这样做有几个问题.首先,您的注射剂可能需要比那些运行时值更长的寿命.但也许更重要的是,您希望能够验证和诊断容器的配置,并且当您开始在对象图中使用运行时值时,这会变得更加麻烦.
所以一般来说有两种解决方案.您可以通过方法调用图传递运行时值,也可以创建一个"上下文"服务,该服务可以在请求时提供此运行时值.
薪火通过调用图运行值是特别有效的解决方案,当你练习像架构这个和这个,你传递的信息通过系统或当运行值可以是服务的合同的明显的部分.在这种情况下,使用消息或方法传递运行时值很容易,并且此运行时值也将通过任何装饰器.
在您的情况下,这将意味着工厂创建IMyService而不传入,IRuntimeValue并且您的代码将此值传递给IMyService使用它指定的方法:
var service = _myServiceFactory.Create();
service.DoYourThing(runtimeValue);
Run Code Online (Sandbox Code Playgroud)
但是,通过调用图传递运行时值并不总是一个好的解决方案.特别是当此运行时值不应该是发送的消息的合同的一部分时.这尤其适用于上下文信息用作有关当前登录用户的信息,当前系统时间等.您不希望传递此信息; 你只是希望它可用.我们不希望这样,因为这会给消费者带来额外的负担,即每次传递正确的值,而他们甚至可能甚至无法更改此信息(将用户放在执行请求的上下文中)实例).
在这种情况下,您应该定义可以注入的服务并允许检索此上下文.例如:
public interface IUserContext {
User CurrentUser { get; }
}
public interface ITimeProvider {
DateTime Now { get; }
}
Run Code Online (Sandbox Code Playgroud)
在这些情况下,当前用户和当前时间不会直接注入构造函数,而是这些服务.需要访问当前用户的组件可以简单地调用_userContext.CurrentUser,这将在构造对象之后完成(读取:不在构造函数内).因此:以懒惰的方式.
但这确实意味着IRuntimeValue必须在MyClass调用之前将其设置在某处.这可能意味着您需要在工厂内进行设置.这是一个例子:
var container = new Container();
var context = new RuntimeValueContext();
container.RegisterSingle<RuntimeValueContext>(context);
container.Register<IMyClassFactory, MyClassFactory>();
container.RegisterDecorator(typeof(IMyClass), typeof(MyClassDecorator));
container.Register<IMyClass, MyClass>();
public class RuntimeValueContext {
private ThreadLocal<IRuntimeValue> _runtime;
public IRuntimeValue RuntimeValue {
get { return _runtime.Value; }
set { _runtime.Value = value; }
}
}
public class MyClassFactory : IMyClassFactory {
private readonly Container _container;
private readonly RuntimeValueContext context;
public MyClassFactory(Container container, RuntimeValueContext context) {
_container = container;
_context = context;
}
public IMyClass Create(IRuntimeValue runtimeValue) {
var instance = _container.GetInstance<IMyClass>();
_context.RuntimeValue = runtimeValue;
return instance;
}
}
public class MyClass : IMyClass {
private readonly RuntimeValueContext _context;
public MyClass(RuntimeValueContext context) {
_context = context;
}
public IRuntimeValue RuntimeValue { get { return _context.Value; } }
}
Run Code Online (Sandbox Code Playgroud)
您也可以MyClass接受IRuntimeValue并进行以下注册:
container.Register<IRuntimeValue>(() => context.Value);
Run Code Online (Sandbox Code Playgroud)
但是不允许验证对象图,因为Simple Injector将确保注册永远不会返回null,但context.Value默认情况下为null.所以另一个选择是执行以下操作:
container.Register<IMyClass>(() => new MyClass(context.Value));
Run Code Online (Sandbox Code Playgroud)
这允许IMyClass验证注册,但在验证期间仍会创建一个MyClass注入空值的新实例.如果MyClass构造函数中有一个guard子句,则会失败.但是,此注册不允许MyClass由容器自动连接.例如,当您有更多的依赖注入时,自动连接该类可以派上用场MyClass.
| 归档时间: |
|
| 查看次数: |
2235 次 |
| 最近记录: |