Search code examples
c#.net-core

How to make service registration dependent on configuration?


I would like to wire the services in the configuration file, something like

builder.Services.AddTransient<MyService>(s => s.GetServiceFromSetting<MyAppSetting>("MyServiceA"));

I could instead use reflection to get the type (although I am not sure if the configuration is available in Startup already, at least with Azure functions there is an issue: https://github.com/Azure/azure-webjobs-sdk/issues/2406).

I was wondering, if there is a better way to get this done.


Solution

  • Presumably your configuration won't change while the application is running. Your configuration is loaded at startup. Dependencies are registered at startup. So startup is an ideal place to decide which implementation you'll use based on configuration.

    Assume you've read some value from configuration. You could use a string, a boolean, whatever. An enum might be a good choice if there are numerous possibilities.

    You've got your value, whatever it is, read from startup. In this case I'll use a boolean.

    if(someFlag)
    {
        builder.Services.AddTransient<ISomeService, SomeImplementation>();
    }
    else
    {
        builder.Services.AddTransient<ISomeService, SomeOtherImplementation>();
    }
    

    Now instead of having code that reads from configuration at runtime (as opposed to startup) the decision is made once. After all, why keep checking a value over and over at runtime if it's determined by a configuration value that doesn't change at runtime?


    You could use the actual type name as a setting:

    var serviceTypeName = Configuration.GetValue<string>("serviceType");
    var serviceType = Type.GetType(serviceTypeName);
    if (typeof(IMyService).IsAssignableFrom(serviceType))
    {
        services.AddTransient<IMyService>(provider => (IMyService)provider.GetRequiredService(serviceType));
    }
    else
    {
        throw new Exception("The type name specified in settings doesn't implement IMyService.");
    }
    

    which in turn can be packaged as a reusable exension:

    public static class RegisterServiceFromConfigurationExtension
    {
        public static IServiceCollection RegisterTransientFromConfiguration<TService>(
            this IServiceCollection serviceCollection,
            IConfiguration configuration,
            string typeSettingName)
        {
            var serviceTypeName = configuration.GetValue<string>(typeSettingName);
            var serviceType = Type.GetType(serviceTypeName);
            if (typeof(IMyService).IsAssignableFrom(serviceType))
            {
                serviceCollection.AddTransient<IMyService>(provider =>
                    (IMyService)provider.GetRequiredService(serviceType));
            }
            else
            {
                throw new Exception($"The type name {serviceTypeName} does not implement {typeof(TService)}.");
            }
    
            return serviceCollection;
        }
    }
    

    so now the whole thing would be boiled down to

    services.RegisterTransientFromConfiguration<ISomeService>(configuration, "settingName");
    

    where "settingName" is the key for the settings value.