使用Simple Injector我可以看到我可以根据设计时可用的信息注册装饰器,但是可以使用运行时数据获得相同的行为吗?
这是一个简单的例子(实际上还有更多装饰器):
public class LineageIdDecorator : IDataReader
{
public LineageIdDecorator(IDataReader dataReader)
{
_dataReader = dataReader;
}
// Implementation skipped...
}
public class RuntimeConfig
{
public bool IncludeLineage { get; set; }
public string ConnectionString { get; set; }
public string Query { get; set; }
}
public class DataSource
{
public IDataReader CreateDataReader(RuntimeConfig config)
{
var connection = new SqlConnection(config.ConnectionString);
var command = new SqlCommand(config.Query, connection);
connection.Open();
IDataReader dataReader = command.ExecuteQuery();
if (config.IncludeLineage)
{
dataReader = new LineageIdDecorator(dataReader);
}
return dataReader;
}
}
重要的部分是:
if (config.IncludeLineage)
{
dataReader = new LineageIdDecorator(dataReader);
}
我是不是总是自己实例化这些装饰器?或者我错过了一些Simple Injector功能?
根据Steven的回答,我现在尝试使用范围和IRuntimeConfigurationProvider来构造装饰器,其中包含启用或禁用所需的信息。我提供了更多背景信息。
我的目标是为内部工作流程系统编写插件。合同如下:
public interface IWorkflowAction<T>
{
async Task<Markdown> Execute(T marshalledData)
}
这份合同是公司范围的,我不能改变它。 Markdown是与合同相同的内部nuget包中提供的类。 T表示我在操作中期望的配置数据类型。主机使用JSON,由用户在网站中配置,并自动将其实现为我指定的类型。
public class MyWorkflowAction : IWorkflowAction<List<RuntimeConfiguration>>
{
private readonly MyActionEngine _engine;
public MyWorkflowAction()
{
container = new Container();
// register components
container.Verify();
_engine = container.GetInstance<MyActionEngine>
}
public async Task<Markdown> Execute(List<RuntimeConfiguration> runtimeConfiguration)
{
foreach (var config in runtimeConfiguration)
{
await _engine.SendAsync(config);
}
_engine.Complete();
await _engine.Completion;
return new Markdown();
}
}
这是我的切入点。我使用SimpleInjector在构造函数中创建MyWorkflowEngine
。 MyWorkflowEngine
是来自TPL DataFlow库的IActionBlock<T>
的实现。
每个请求都使用SendAsync
排队,并根据app.config值配置MaxDOP的方式并行执行。
MyActionEngine
内的代码是手动构建一个IDataReader
并根据配置对象中的值应用所需的装饰器。
一旦所有工作排队,操作块就会被告知不再需要更多数据。然后我们等待完成并退出。
我很清楚我需要使用AsyncScopedLifestyle
,但如果它们依赖于IRuntimeProviderContext,我仍然不清楚如何在运行时构造装饰器。取决于配置对象的当前实例。
答案 0 :(得分:1)
根据Simple Injector中的运行时信息,不能根据运行时信息有条件地应用装饰器并非如此,但这是一种不鼓励的做法。
不鼓励根据运行时信息进行注册,因为它使您的配置难以验证,因为验证取决于能否构建对象图,这通常是不可能的,因为这些所需的运行时信息通常不可用于验证时间(可能是在应用程序启动时或运行测试套件时)。
相反,您不应该根据运行时信息更改对象图的结构,而是使用此运行时信息来决定对已构建的对象图执行哪个调用图。
由于这种沮丧,内置的装饰器工具不允许根据运行时信息有条件地注册装饰器。在Github存储库的代码示例项目中有examples如何进行基于运行时的装饰,但它们仅仅是示例,我建议不要使用它们。
不是在运行时有条件地应用装饰器,而是不断应用装饰器,并根据装饰器在调用时检索的运行时数据,在装饰器内部实现分支 。
这可能如下所示:
public class LineageIdDecorator : IDataReader
{
public LineageIdDecorator(
IDataReader decoratee, IRuntimeConfigProvider configProvider) { .. }
// IDataReader methods
public object DoSomething()
{
if (configProvider.Config.IncludeLineage)
{
// run decorated behavior
}
return decoratee.DoSomething();
}
}
这里引入了一个新的抽象IRuntimeConfigProvider
,允许在运行时检索运行时配置,反对将其注入构造函数。
另一种方法是分割运行时选择行为和装饰器的实际行为。当装饰器包含大量逻辑时,这可能很重要。将它们分成两个装饰器会使每个装饰者都有一个责任。这会将LineageIdDecorator
缩减回原始实现,第二个实现可能看起来像这样:
public class RuntimeDecoratorSelector : IDataReader
{
private readonly IDataReader decoratedDecoratee;
private readonly IDataReader originalDecoratee;
private readonly IRuntimeConfigProvider configProvider;
public RuntimeDecoratorSelector(
IDataReader decoratedDecoratee, IDataReader originalDecoratee,
IRuntimeConfigProvider configProvider)
{
this.decoratedDecoratee = decoratedDecoratee;
this.originalDecoratee = originalDecoratee;
this.configProvider = configProvider;
}
private IDataReader Decoratee =>
configProvider.Config.IncludeLineage ? decoratedDecoratee : originalDecoratee;
// IDataReader methods
public object DoSomething()
{
return Decoratee.DoSomething();
}
}
作为原始服务的装饰服务都与IRuntimeConfigProvider
一起注入此选择器类。
注册这个新的RuntimeDecoratorSelector以及原始的LineageIdDecorator和实现现在变得有点复杂,因为与装饰器相比,它涉及进行条件注册。以下是如何进行这些注册:
container.RegisterConditional<IDataReader, DataReaderImpl>(
c => c.Consumer?.ImplementationType == typeof(LineageIdDecorator)
|| c.Consumer?.Target.Name.StartsWith("original") == true);
container.RegisterConditional<IDataReader, LineageIdDecorator>(
c => c.Consumer?.Target.Name.StartsWith("decorated") == true);
container.RegisterConditional<IDataReader, RuntimeDecoratorSelector>(c => !c.Handled);
我们在这里做的是有条件地注册DataReaderImpl
并告诉它注入LineageIdDecorator
或注入参数名称的任何构造函数参数(类型IDataReader
)使用原始。这将是RuntimeDecoratorSelector
。
LineageIdDecorator
是有条件注册的,但是它被指示注入任何构造函数参数(类型IDataReader
),其中参数名称以 decorated 开头。这显然是RuntimeDecoratorSelector
的第二个参数。
最后但并非最不重要的是,我们正在注册RuntimeDecoratorSelector
。也许令人惊讶的是,它必须有条件地注册。这是因为Simple Injector非常严格,并且会检测多个注册重叠的时间。它迫使你非常清楚你想要什么。不使此注册成为条件,将使其适用于其自己的构造函数参数,这将导致循环依赖。通过声明应将其注入任何消费者,当尚未处理的注册时,我们将此注册作为后备并防止对象图变为循环或模糊。
如此长的故事,如果您希望根据运行时条件阻止构建对象图,则应该将选择逻辑添加到装饰器或创建单独的选择器“装饰器”。如果您无论在构造对象图时如何应用装饰器,您都可以使用CodeSamples项目的运行时装饰器示例。