C# 如何装饰依赖于运行时值进行创建的类
虽然我已经使用Ninject很长时间了,但我对使用Simple Injector还是很陌生,所以总体上我对DI很满意。吸引我使用SimpleInjector的一个原因是装饰器的易用性 在请求服务时解决依赖关系的所有正常情况下,我都能够成功地使用带有简单注入器的装饰器。然而,当必须使用运行时值构造服务时,我很难弄清楚是否有一种方法可以应用我的decorator 在Ninject中,我可以将一个C# 如何装饰依赖于运行时值进行创建的类,c#,dependency-injection,decorator,simple-injector,C#,Dependency Injection,Decorator,Simple Injector,虽然我已经使用Ninject很长时间了,但我对使用Simple Injector还是很陌生,所以总体上我对DI很满意。吸引我使用SimpleInjector的一个原因是装饰器的易用性 在请求服务时解决依赖关系的所有正常情况下,我都能够成功地使用带有简单注入器的装饰器。然而,当必须使用运行时值构造服务时,我很难弄清楚是否有一种方法可以应用我的decorator 在Ninject中,我可以将一个构造函数参数传递给内核。我想不出用简单的注入器复制的方法 我在下面放了一些非常基本的代码来说明。在现实世界
构造函数参数
传递给内核。我想不出用简单的注入器复制的方法
我在下面放了一些非常基本的代码来说明。在现实世界中,我想做的是将IMyClassFactory
实例传递到我的应用程序中的其他类中。然后,这些其他类可以使用它们提供的IRuntimeValue
来使用它创建IMyClass
实例。他们从IMyClassFactory
获得的IMyClass
实例将由注册的装饰者自动装饰
我知道我可以在我的IMyClassFactory
或我能想到的任何Func
中手动应用我的装饰程序,但我希望它“正常工作”
我不停地试图抽象出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;
}
}
}
我的问题是,我希望我的iccustomerviewmodel
由容器装饰,而更新它则绕过了这一点。现在我知道如何绕过这个限制
所以我想我接下来的问题是:我的设计一开始是错的吗?我真的觉得应该将iccustomer
传递到CustomerViewModel
的构造函数中,因为这表明它是必需的、得到验证的,等等。我不想在事后添加它 Simple Injector明显不支持通过GetInstance
方法传递运行时值。原因是在构建对象图时不应使用运行时值。换句话说,的构造函数不应该依赖于运行时值。这样做有几个问题。首先,注射剂可能需要比那些运行时值活得更长。但是,也许更重要的是,您希望能够在对象图中使用运行时值,并且能够更改容器的配置,这会变得更加麻烦
一般来说,有两种解决方案。要么通过方法调用图传递运行时值,要么创建一个“上下文”服务,在请求时提供此运行时值
当您实践通过系统传递消息的体系结构时,或者当运行时值可能是服务契约的一个明显部分时,通过调用图传递运行时值尤其是一个有效的解决方案。在这种情况下,很容易通过消息或方法传递运行时值,并且该运行时值也将在传递过程中通过任何装饰器
在您的情况下,这意味着工厂创建IMyService
,而不传入IRuntimeValue
,并且您的代码使用指定的方法将此值传递给IMyService
:
var service = _myServiceFactory.Create();
service.DoYourThing(runtimeValue);
然而,通过调用图传递运行时值并不总是一个好的解决方案。尤其是当此运行时值不应成为所发送消息的协定的一部分时。这尤其适用于上下文信息,用作有关当前登录用户、当前系统时间等的信息。您不希望传递这些信息;你只是想让它可用。我们不希望这样,因为这会给消费者带来额外的负担,让他们每次都传递正确的值,而他们甚至可能无法更改此信息(以执行请求的用户为例)
在这种情况下,您应该定义可以注入并允许检索此上下文的服务。例如:
public interface IUserContext {
User CurrentUser { get; }
}
public interface ITimeProvider {
DateTime Now { get; }
}
在这些情况下,当前用户和当前时间不会直接注入构造函数,而是注入这些服务。需要访问当前用户的组件只需调用\u userContext.CurrentUser
,这将在构建对象后完成(读取:而不是构造函数内部的)。因此:以一种懒惰的方式
但是,这意味着必须在调用MyClass
之前的某个位置设置IRuntimeValue
。这可能意味着您需要在工厂内进行设置。下面是一个例子:
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; } }
}
但是不允许验证对象图,因为Simple Injector将确保注册永远不会返回null,但是默认情况下,context.Value
将为null。因此,另一个选择是执行以下操作:
container.Register<IMyClass>(() => new MyClass(context.Value));
container.Register(()=>newmyclass(context.Value));
这允许验证IMyClass
注册,但在验证期间仍将创建一个新的MyClass
实例,该实例将被注入空值。如果在MyClass
构造函数中有一个guard子句,则此操作将失败。但是,此注册不允许容器自动连接MyClass
。例如,当您有更多依赖项要注入到MyClass
中时,自动连接该类会很方便。此外,如果服务依赖于
container.Register<IRuntimeValue>(() => context.Value);
container.Register<IMyClass>(() => new MyClass(context.Value));