在 abp.io .NET5/6/Core 中注入多个实现
Inject multiple implementations in abp.io .NET5/6/Core
更新 2:修复了最后的代码
我有下面的 abp.io 服务,在通过 DI 实例化的构造函数中有 2 个参数。
其中之一 IOutcomeWriter
有 2 个实现。
我想在运行时定义要使用 IOutcomeWriter
的哪个实现。
这是主要服务:
public class UCManagerService
: DomainService, IUCManagerService, ITransientDependency {
private readonly IUCInputReader _inputReader;
// This field can have 2 or 3 implementations.
private readonly IOutcomeWriter _outcomeWriter;
public UCManagerService(
IUCInputReader inputReader, IOutcomeWriter outcomeWriter) {
_inputReader = inputReader;
_outcomeWriter = outcomeWriter;
}
public async Task ExecuteAsync() {
// start processing the input and generate the output
var input = _inputReader.GetInput());
// do something
// ...
_outcomeWriter.Write(something);
}
}
主要服务与 IUCInputReader
和 IOutcomeWriter
的 2 个实现一起在 AbpModule
中注册:
[DependsOn(
typeof(SwiftConverterDomainModule),
typeof(AbpAutofacModule) // <= use Autofac in some way (I don't know how)
)]
public class ProgramAppModule : AbpModule {
public override void ConfigureServices(ServiceConfigurationContext context) {
context.Services.AddTransient<IUCManagerService, UCManagerService>();
context.Services.AddTransient<IUCInputReader, UCInputReader>();
// 2 implementations of IOutcomeWriter
context.Services.AddTransient<IOutcomeWriter, OutcomeWriter1>();
context.Services.AddTransient<IOutcomeWriter, OutcomeWriter2>();
}
}
我想要的是根据 appsettings.json
:
中的一些值有时用 OutcomeWriter1
有时用 OutcomeWriter2
实例化 UCManagerService
IList<JobSetting> jobsToSet = _configuration.GetSection("Jobs")
.Get<List<JobSetting>>();
foreach (JobSetting jobToSet in jobsToSet) {
// If jobsToSet.SomeValue == 'MyValue1' following line should have to
// require a IUCManagerService using OutcomeWriter1. If it is
// 'MyValue2' it'd use OutcomeWriter2, and so on:
var service = abpApplication.ServiceProvider.GetRequiredService<IUCManagerService>(); // ???
// do something else with service
// ...
}
最后,如果明天我添加一个 OutcomeWriter3
我只想在 ProgramAppModule.ConfigureServices(...)
中注册它,当然在 appsettings.json
.
中使用不同的密钥
如果我没理解错的话,你需要 IOutcomeWriter
根据当前执行的作业而有所不同。换句话说,这意味着您需要动态地根据其上下文切换编写器。
事实上你需要动态地改变它,这意味着这不是一个可以单独使用你的 DI 配置解决的问题,因为 DI 配置最好保持 静态.
相反,您需要混合搭配一些概念。首先,您需要一种在上下文中设置使用的作业的方法。例如:
// DI configuration
services.AddScoped<JobContext>();
// Execution of a job
using (var scope = abpApplication.ServiceProvider.CreateScope())
{
var context = scope.GetRequiredService<JobContext>();
context.CurrentJob = typeof(MyFirstJob);
var job = scope.GetRequiredService<MyFirstJob>();
var job.Execute();
}
在此示例中,JobContext
是一个 class,它包含在执行某个作业期间使用的数据。它被注册为 Scoped
以允许此数据可用于同一范围内的多个 classes。
现在使用这个新的 JobContext
,您可以为 IOutcomeWriter
构建一个适配器,它可以根据其注入的 JobContext
将传入调用转发到正确的实现。这可能如下所示:
public class JobSpecificOutcomeWriter : IOutcomeWriter
{
private readonly JobContext context;
private readonly IList<JobSetting> settings;
private readonly IEnumerable<IOutcomeWriter> writers;
public JobSpecificOutcomeWriter(
JobContext context,
IList<JobSetting> settings,
IEnumerable<IOutcomeWriter> writers)
{
this.context = context;
this.settings = settings;
this.writers = writers;
}
// Implement all IOutcomeWriter methods by forwarding them to the
// CurrentWriter.
object IOutcomeWriter.SomeMethod(object a) =>
this.CurrentWriter.SomeMethod(a);
private IOutcomeWriter CurrentWriter
{
get
{
// TODO: Based on the current context and the settings,
// select the proper outcome writer from the writers list.
}
}
}
当 JobSpecificOutcomeWriter
被注入到 UCManagerService
(或与此相关的任何组件)时,它透明地允许使用正确的编写器,而无需消耗 class 知道这一点.
实际上,棘手的部分是现在使用 JobSpecificOutcomeWriter
正确配置您的 DI 容器。根据您使用的 DI 容器,您的里程可能会有所不同,并且使用 MS.DI 容器,这实际上非常复杂。
services.AddTransient<IOutcomeWriter>(c =>
new JobSpecificOutcomeWriter(
context: c.GetRequiredService<JobContext>(),
settings: jobsToSet,
writers: new IOutcomeWriter[]
{
c.GetRequiredService<MyFirstJob>(),
c.GetRequiredService<MySecondJob>(),
c.GetRequiredService<MyThirdJob>(),
});
services.AddTransient<MyFirstJob>();
services.AddTransient<MySecondJob>();
services.AddTransient<MyThirdJob>();
更新 2:修复了最后的代码
我有下面的 abp.io 服务,在通过 DI 实例化的构造函数中有 2 个参数。
其中之一 IOutcomeWriter
有 2 个实现。
我想在运行时定义要使用 IOutcomeWriter
的哪个实现。
这是主要服务:
public class UCManagerService
: DomainService, IUCManagerService, ITransientDependency {
private readonly IUCInputReader _inputReader;
// This field can have 2 or 3 implementations.
private readonly IOutcomeWriter _outcomeWriter;
public UCManagerService(
IUCInputReader inputReader, IOutcomeWriter outcomeWriter) {
_inputReader = inputReader;
_outcomeWriter = outcomeWriter;
}
public async Task ExecuteAsync() {
// start processing the input and generate the output
var input = _inputReader.GetInput());
// do something
// ...
_outcomeWriter.Write(something);
}
}
主要服务与 IUCInputReader
和 IOutcomeWriter
的 2 个实现一起在 AbpModule
中注册:
[DependsOn(
typeof(SwiftConverterDomainModule),
typeof(AbpAutofacModule) // <= use Autofac in some way (I don't know how)
)]
public class ProgramAppModule : AbpModule {
public override void ConfigureServices(ServiceConfigurationContext context) {
context.Services.AddTransient<IUCManagerService, UCManagerService>();
context.Services.AddTransient<IUCInputReader, UCInputReader>();
// 2 implementations of IOutcomeWriter
context.Services.AddTransient<IOutcomeWriter, OutcomeWriter1>();
context.Services.AddTransient<IOutcomeWriter, OutcomeWriter2>();
}
}
我想要的是根据 appsettings.json
:
OutcomeWriter1
有时用 OutcomeWriter2
实例化 UCManagerService
IList<JobSetting> jobsToSet = _configuration.GetSection("Jobs")
.Get<List<JobSetting>>();
foreach (JobSetting jobToSet in jobsToSet) {
// If jobsToSet.SomeValue == 'MyValue1' following line should have to
// require a IUCManagerService using OutcomeWriter1. If it is
// 'MyValue2' it'd use OutcomeWriter2, and so on:
var service = abpApplication.ServiceProvider.GetRequiredService<IUCManagerService>(); // ???
// do something else with service
// ...
}
最后,如果明天我添加一个 OutcomeWriter3
我只想在 ProgramAppModule.ConfigureServices(...)
中注册它,当然在 appsettings.json
.
如果我没理解错的话,你需要 IOutcomeWriter
根据当前执行的作业而有所不同。换句话说,这意味着您需要动态地根据其上下文切换编写器。
事实上你需要动态地改变它,这意味着这不是一个可以单独使用你的 DI 配置解决的问题,因为 DI 配置最好保持 静态.
相反,您需要混合搭配一些概念。首先,您需要一种在上下文中设置使用的作业的方法。例如:
// DI configuration
services.AddScoped<JobContext>();
// Execution of a job
using (var scope = abpApplication.ServiceProvider.CreateScope())
{
var context = scope.GetRequiredService<JobContext>();
context.CurrentJob = typeof(MyFirstJob);
var job = scope.GetRequiredService<MyFirstJob>();
var job.Execute();
}
在此示例中,JobContext
是一个 class,它包含在执行某个作业期间使用的数据。它被注册为 Scoped
以允许此数据可用于同一范围内的多个 classes。
现在使用这个新的 JobContext
,您可以为 IOutcomeWriter
构建一个适配器,它可以根据其注入的 JobContext
将传入调用转发到正确的实现。这可能如下所示:
public class JobSpecificOutcomeWriter : IOutcomeWriter
{
private readonly JobContext context;
private readonly IList<JobSetting> settings;
private readonly IEnumerable<IOutcomeWriter> writers;
public JobSpecificOutcomeWriter(
JobContext context,
IList<JobSetting> settings,
IEnumerable<IOutcomeWriter> writers)
{
this.context = context;
this.settings = settings;
this.writers = writers;
}
// Implement all IOutcomeWriter methods by forwarding them to the
// CurrentWriter.
object IOutcomeWriter.SomeMethod(object a) =>
this.CurrentWriter.SomeMethod(a);
private IOutcomeWriter CurrentWriter
{
get
{
// TODO: Based on the current context and the settings,
// select the proper outcome writer from the writers list.
}
}
}
当 JobSpecificOutcomeWriter
被注入到 UCManagerService
(或与此相关的任何组件)时,它透明地允许使用正确的编写器,而无需消耗 class 知道这一点.
实际上,棘手的部分是现在使用 JobSpecificOutcomeWriter
正确配置您的 DI 容器。根据您使用的 DI 容器,您的里程可能会有所不同,并且使用 MS.DI 容器,这实际上非常复杂。
services.AddTransient<IOutcomeWriter>(c =>
new JobSpecificOutcomeWriter(
context: c.GetRequiredService<JobContext>(),
settings: jobsToSet,
writers: new IOutcomeWriter[]
{
c.GetRequiredService<MyFirstJob>(),
c.GetRequiredService<MySecondJob>(),
c.GetRequiredService<MyThirdJob>(),
});
services.AddTransient<MyFirstJob>();
services.AddTransient<MySecondJob>();
services.AddTransient<MyThirdJob>();