在 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);
    }
}

主要服务与 IUCInputReaderIOutcomeWriter 的 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>();