user13755987
user13755987

Reputation:

Add multiple services to the IServiceCollection with a loop using reflection

During the DI container setup I have multiple services that rely on a manual setup. E.g. some of them require a database connection string. So instead of writing

services.AddTransient(typeof(TheInterface), typeof(TheImplementation));

I have to do

services.AddTransient<TheInterface>(serviceProvider => 
{
    // gather all the constructor parameters here
    return new TheImplementation(/* pass in the constructor parameters */);
});

The constructor parameters are always the same. Instead of writing this multiple times I thought about creating a collection of those services and looping through that collection. I know I can create new instances from a type by using the Activator class but I'm struggling with the conversion from a type to a generic. The following code is a sample code for demo purposes

    public void SetupServices(IServiceCollection services, string databaseConnectionString)
    {
        IDictionary<Type, Type> servicesTypes = new Dictionary<Type, Type>()
        {
            { typeof(MyInterface), typeof(MyImplementation) }
            // ... more services here
        };

        foreach (KeyValuePair<Type, Type> servicesType in servicesTypes)
        {
            services.AddTransient <??? /* servicesType.Key does not work here */> (serviceProvider =>
            {
                return Activator.CreateInstance(servicesType.Value, databaseConnectionString /*, other params */);
            });
        }
    }

The position I'm struggling is this line

services.AddTransient <??? /* servicesType.Key does not work here */>

How would I convert the service interface type to a generic one? I'm not even sure if this loop is worth the effort... but currently I have 5 services which could use it.

Upvotes: 1

Views: 1717

Answers (2)

JonasMH
JonasMH

Reputation: 1183

I'm not sure doing a generic version of it is the correct way to go. Could your 'manual setup' of your services may be fixed using the options pattern? As it looks like you're trying to pass through a database connection string.

It could look something like

// Options object
public class DatabaseOptions {
   public string ConnectionString {get;set;}
}

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    [...]
    // From configuration (appsettings.json)
    services.Configure<DatabaseOptions>(Configuration.GetSection("Database"));
    // OR directly
    services.Configure<DatabaseOptions>(x => x.ConnectionString = "SomeString");

}

// The service
public class SomeService {
    public SomeService(IOptions<DatabaseOptions> dbOptions) {
        var connectionString = dbOptions.Value.ConnectionString;
    }
}

Upvotes: 2

Roman
Roman

Reputation: 12171

You can register type using ServiceDescriptor:

foreach (KeyValuePair<Type, Type> servicesType in servicesTypes)
{ 
    services.Add(new ServiceDescriptor(
        serviceType.Key, // interface
        serviceProvider => Activator.CreateInstance(servicesType.Value, databaseConnectionString /*, other params */), // implementation
        ServiceLifetime.Transient); // lifetime
    }
}

Upvotes: 1

Related Questions