0

I'm working on an application that will run on multiple systems and may use different modules to communicate with external systems, but on each system, only one module will be used at a time. As it should be possible to change the used module on a specific system, the same application with all modules should be deployed to all systems. For simplicity, let's assume that there are two modules called Foo and Bar.

Both modules have their own module descriptor that registers the module components to the dependency injection container:

public class FooModule : IModule
{
    public void Configure(IServiceCollection services)
    {
        services.AddTransient<IService, FooService>();
        // Register dependencies of FooService
    }
}

I know that Autofac supports modules out of the box (even with support for configuration) and there are several libraries that add such a feature to Microsoft.Extensions.DependencyInjection, but I want to ask this question with a general look at the concept of dependency injection.

If the services of all modules should be used at the same time, I would be done. Given they implement the same service, I could inject them using IEnumerable<IService>. But in my use case, there is a component that requires a single IService and I want to select the implementation based on a configuration (e.g. from a file).

Now there are several approaches where to apply that configuration and I'm not sure which one should be preferred:

1st approach - load single assembly

I could read the configuration and then load only the external assembly that contains the IModule that should be used. This would require the introduction of some "magic" link between the configuration values and the names of the module assemblies, as the core application should not know the extension modules beforehand.

2nd approach - call single module

All the assemblies are loaded, but using a link between the configuration values and the names of the module classes (or namespaces), only the one module that should be used will be called to setup the passed IServiceCollection.

3rd approach - let the module decide

The modules decide on their own if they are configured and therefor should provide their service implementation. Instead of evaluating the configuration in the core application, the configuration gets passed to the modules:

public class FooModule : IModule
{
    public void Configure(IServiceCollection services, IConfiguration configuration)
    {
        if (configuration.GetSection("foo").Exists())
        {
            services.AddTransient<IService, FooService>();
        }
    }
}

4th approach - use some DI container feature

I know that Autofac or other DI containers support named / keyed service registrations that would basically solve this problem and therefor answer this question for me. However, I guess there is a reason why other DI containers like Microsoft.Extensions.DependencyInjection do not provide this feature.


What approach would you prefer? Is there an approach or some variation that I missed? Is there anything else I should keep in mind when designing a system in that way?

5
  • unity has xml configuration options.
    – Ewan
    Commented Jul 9, 2020 at 15:42
  • @Ewan Actually, I want to avoid using a configuration system just because I am forced by my dependency container. Autofac at least integrates into Microsoft.Extensions.Configuration, but then I would be bound to their keys for registering modules or components. Commented Jul 9, 2020 at 16:04
  • @LukasKörfer I think the following two questions could help you to narrow down the possibilities. 1) How dynamic should be the module resolution? (Is it okay to load the necessary module(s) at startup time or do you need to decide that at runtime?) 2) How much coupling is acceptable for you? (How often can the mapping change? When they change are they changing together or separately?) Commented Jul 10, 2020 at 14:01
  • 2
    This question at stackoverflow seems to be asking roughly the same thing - stackoverflow.com/questions/22701412/… Commented Jul 11, 2020 at 0:10
  • @BenCottrell Thanks, it is indeed a similar question with a lot of high-quality answers, so probably I will find a solution to my problem. Commented Jul 11, 2020 at 0:30

1 Answer 1

1

You can achieve very easily the feature to register types from a configuration file:

modulesettings.json:

{
  "Dependencies": {
    "A.Namespace.IServiceType1, A.Dll": "Another.Namespace.ImplementationType1, Another.Dll",
    "Still.Different.Namespace.IServiceType2, Still.Different.Dll": "Namespace.ImplementationType2, Dll"
  }
  
}

add modulesettings.json to your configuration in startup.cs

var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
             .AddJsonFile("modulesettings.json", optional: false)

and register the dependencies

public void ConfigureServices(IServiceCollection services)
        {
            Dictionary<string, string> moduleSettings = new Dictionary<string, string>();
            Configuration.GetSection("Dependencies").Bind(moduleSettings);
            
            foreach(var x in moduleSettings)
            {
                Type serviceType = Type.GetType(x.Key);
                Type implementationType = Type.GetType(x.Value);
                services.AddSingleton(serviceType, implementationType);
            }
            
            ...
        }

while doing "Type serviceType = Type.GetType(x.Key);" if the assembly containing the specified type is not loaded yet it will be loaded while providing the type.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.