Search code examples
c#dependency-injection.net-coreautofacautofac-module

Autofac in a Multi-Project Solution


I'm using Autofac as a dependency injection system in a C# solution that spans several class libraries and several executables. I'm using modules to configure Autofac, but that still leaves me with the issue of building the DI container, which varies depending upon which executable I'm composing it for.

I tried using Autofac's RegisterAssemblyModules, but you have to give it a list of assemblies to scan, and until some Type in a class library assembly is used, the assembly isn't loaded, and hence not available to scan.

Some people recommend loading every assembly in the bin directory which might have an Autofac module definition in it. But that seems to pose the risk of an undesired assembly getting slipped into action.

So what I came up with is this static class, which is defined in a common class library:

public static class Container
{
    private static IContainer _instance;
    private static Dictionary<string, Assembly> _moduleAssemblies = new Dictionary<string, Assembly>();

    public static void RegisterAutofacModuleAssembly<T>()
        where T : class
    {
        var assembly = typeof(T).Assembly;

        if( !_moduleAssemblies.ContainsKey( assembly.FullName ) )
            _moduleAssemblies.Add( assembly.FullName, assembly );
    }

    public static IContainer Instance
    {
        get
        {
            if( _instance == null )
            {
                var builder = new ContainerBuilder();

                builder.RegisterAssemblyModules( _moduleAssemblies.Select( ma => ma.Value ).ToArray() );

                _instance = builder.Build();
            }

            return _instance;
        }
    }
}

You use this by including lines like this in the startup code of an application:

public static void Main(string[] args)
{
  AutoFacRegistrar.Container.RegisterAutofacModuleAssembly<ScannerApp>(); 
  AutoFacRegistrar.Container.RegisterAutofacModuleAssembly<AppConfiguration>();

Is this a reasonable solution? If there's a better, more flexible one I'd be interested in learning about it.

Issue in IOptions<> Binding System

In implementing @Nightowl888's suggestion, I ran into a problem with the Microsoft configuration IOptions<> system. Here's how I'm trying to configure Autofac to resolve AppConfiguration objects:

protected override void Load( ContainerBuilder builder )
{
    base.Load( builder );

    var config = new ConfigurationBuilder()
        .AddJsonFile( AppConfiguration.WebJobsConfigFile, false )
        .AddUserSecrets<ConfigurationModule>()
        .AddEnvironmentVariables()
        .Build();

    builder.Register<AppConfiguration>( ( c, p ) =>
        {
            var retVal = new AppConfiguration( c.Resolve<ILogger>() );

            config.Bind( retVal );

            return retVal;

        } )
        .SingleInstance();
}

The problem occurs in the call to Bind(). As it traverses and parses the configuration information, it expects to create various objects thru parameterless constructors...which makes it difficult to use constructor injectcion.

If I can't use constructor injection, I need to be able to resolve against the DI container within constructor code. I don't see how I can define a library assembly which doesn't hardwire in a particular DI container's resolution semantics.

Thoughts? Other than "abandon the IOptions<> system", which I've considered, but it provides a number of benefits I'd like to maintain.


Solution

  • Every executable application should have its own unique DI configuration. Libraries and frameworks should be built to be DI-friendly, but not actually reference any DI container.

    A composition root is an application's configuration. Sharing it between applications is similar to sharing a .config file between applications - that is, it is not usually done. See Composition Root Reuse.

    If you are going to use autofac modules, they should be a part of the application that uses them, not included in the assembly with the components that are being composed. While it may seem like you are gaining something by not having to repeat the configuration code in every application, the main issue with doing this is that it means your application has lost one of the main benefits of DI - that is, it cannot provide an alternative implementation of any given component. The whole point of making a library loosely-coupled is that it allows the decision of how the application will be coupled together to ultimately be made by the application that hosts the components.

    Pet peeve: Also note that how many projects or solutions you have has absolutely nothing to do with the runtime behavior of an application (such as how it is composed). Projects and solutions are a way to organize code before it is compiled - once the code is compiled, there is no concept of "project" or "solution", all you are left with are "assemblies" that may depend on other "assemblies". For each application you end up with an executable assembly and 0 or more dependent assemblies. The composition root should only exist in the executable assembly.