0

UPDATE 2: FIXED THE CODE at the end

I have the abp.io service below with 2 parameters in the constructor instantiated via DI. One of them, IOutcomeWriter, has 2 implementations.

I'd like to define at runtime which of the implementations of IOutcomeWriter to use.

This is the main service:

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

The main service is registered in the AbpModule together with with IUCInputReader and the 2 implementations of IOutcomeWriter:

[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>();
    }
}

What I would like is to instantiate UCManagerService sometimes with OutcomeWriter1 and sometimes with OutcomeWriter2, according to some values in appsettings.json:

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
    // ...
}

Finally, if a tomorrow I add an OutcomeWriter3 I would just like to register it in ProgramAppModule.ConfigureServices(...) and of course use a different key in appsettings.json.

3
  • You are stating that you want to switch the IOutcomeWriter implementation based on the Jobs configuration setting, but does its value change while the application is running, or will the value be constant during the applications lifetime? In other words, is it okay to restart the application if the Jobs configuration setting needs to change? Commented Mar 14, 2022 at 8:31
  • It's unclear whether the code comment <= use Autofac in some way (I don't know how) is relevant to your question or not. Is this something your question is about and which an answer to? If so, you might want to add more context or move that part to a new question on SO. If it is irrelevant, you might want to remove the comment, as it would only be confusing. Commented Mar 14, 2022 at 9:47
  • @Steven, your comments are right. Code fixed. About the questions: - Jobs configuration setting change: ok to restart. Alternative: clean and re-init the DI regs. The key point is that I might have N jobs, 1 requiring OutcomWriter1, the 2nd OutcomWriter2, 3rd OutcomWriter1 again, ... - Autofac: this is not clear to be. It is in used the abp.io framework I use. The point is that I'm still learning it. Commented Mar 15, 2022 at 18:22

1 Answer 1

1

If I understand correctly, you need the IOutcomeWriter to differ based on the currently executed job. In other words, that means that you need to dynamically switch the writer based on its context.

The fact that it you need to change it dynamically, it means that is not a problem that can be solved solely using your DI configuration, because DI configurations are best kept static.

Instead, you need to mix and match a few concepts. First of all, you need a way to set the used job in the context. For instance:

// 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();
}

In this example, JobContext is a class that holds the data that is used during the execution of a certain job. It is registered as Scoped to allow this data to be available for multiple classes within the same scope.

Now using this new JobContext, you can build an adapter for IOutcomeWriter that can forward the incoming call to the right implementation based on its injected JobContext. This might look as follows:

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.
        }
    }
}

When JobSpecificOutcomeWriter is injected into UCManagerService (or any component for that matter), it transparently allows the proper writer to be used, without the consuming class from knowing about this.

The tricky part, actually, is to now configure your DI container correctly using JobSpecificOutcomeWriter. Depending on which DI Container you use, your mileage might vary and with the MS.DI Container, this is actually quite complicated.

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>();
Sign up to request clarification or add additional context in comments.

4 Comments

Unfortunately this cannot be the solution. What about jobsToSet[0] needs OutcomeWriter1 and jobsToSet[1] requires OutcomeWriter2? I modified again the question to let it be more clear. Tnx
In this case you might want to rephrase your question to explain what it is you are trying to achieve. For instance, show the exact graphs you are trying to create and explain how this relates to the configuration you defined.
did it right now
See my update..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.