简体   繁体   中英

ASP.NET Core Create class instance where class may use DI - looking for pattern advice

I have an issue where I need to instantiate a class with some configuration data but that class may use other classes that it gathers from the DI container. I have many services registered in Startup that I can access from controllers and services with no issues - working well.

The problem is I have an interface, IProvider , that has a number of classes that implement it ProviderA , ProviderB etc. At runtime, based on the users choice, I need to create an instance of one of these classes and call some methods on it.

The class requires some configuration data (they are used to communicate with external systems) and I need to work out how to pass the config data as well as allow the DI to work for those classes that require some other services.

The IProvider interface is defined as:

public interface IProvider
{
    Task<string> ValidateAsync();
}

A class that uses this may be like this:

public class ProviderA: IProvider
{
    private readonly SMSService _smsService;

    public ProviderA(SMSService smsService, string configuration)
    {
        _smsService = smsService;
        //do something with configuration
    }

    public Task<string> ValidateAsync()
    {
        //validate connection using passed in configuration
        throw new NotImplementedException();
    }
}

Based on user input I need to create a new instance of the class but want the class to work out its own dependencies ( SMSService in this case). I also note that SMSService may also have other dependencies ( dbContext etc).

My code for creating the new instance is to work out the type of the object and to create an instance:

provider = new ProviderA(configuration);

Any ideas on how do I create an instance of this object, pass in params, and allow it to obtain its own dependencies? I suspect that my approach is incorrect but in the past, it has worked well but now with DI it is giving me trouble as the various services need to be sourced AND the params need to be passed.

I would suggest add some description field to IProvider interface, eg:

public interface IProvider
{
    Task<string> ValidateAsync();

    ProviderType Type { get; }
}

It allows you to inject all implementation of the interface and select one of them based on user input:

public class MyService
{
    private IEnumerable<IProvider> providers;

    public MyService(IEnumerable<IProvider> providers)
    {
        this.providers = providers;
    }

    public Task Action(UserInput input)
    {
        var provider = providers.FirstOrDefault(el => el.Type == SmsProvider);
    }
}

As for configuration, you can create Config class for each type of Provider and register it:

public class Config
{
    public string SomeConfig { get; set; }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Config());
}

public ProviderA(SMSService smsService, Config configuration)
{

}

Or even put the config in appsettings.json and use IOptions pattern

public void ConfigureServices(IServiceCollection services)
{
    var section = Configuration.GetSection("ProviderA");
    services.Configure<Config>(section);
}

and consume it:

public ProviderA(SMSService smsService, IOptions<Config> configuration)
{
    var config = configuration.Value;
}

This is a perfect scenario for a factory pattern. You inject your factory, and then at runtime, you get the actual instance you need from that. For example:

public class ProviderFactory
{
    public ProviderFactory(...) { ... } // Inject all the stuff you need here

    public IProvider CreateProvider(string type)
    {
        // switch on `type`, new up the right provider, and return it
    }
}

There's of course other things you can do here. You might want to new up all your providers once during construction, or you can use something like Lazy<> to new them up only when you access them. Or, you can use something like a ConccurrentDictionary to store instances as you create them. It's largely up to you and the needs of your application. The main point is to have this factory class where you can pull out the right provider instance you need. Inject that factory and you're good to go.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM