Search code examples
c#.net-coredependency-injectioninversion-of-controlautofac

Howto use external plugins via config file (runtime configuration) with Autofac 5


I'm trying to setup a simple .NET Core 3.1 project that uses Autofac 5 IoC Container.

I've added a reference to latest Autofac.Configuration 5.0.0 package.

I've created interface, implementation and test app with a simple JSON config (following this guide):

{
  "components": [
    {
      "type": "Implementations.ImplementationN, Implementations",
      "services": [
        {
          "type": "Interfaces.InterfaceN, Interfaces"
        }
      ]
    }
  ]
}

I use the following code (according to the same guide):

using Interfaces;
using Autofac;
using Autofac.Configuration;
using Microsoft.Extensions.Configuration;
using System;

------------------------
var config = new ConfigurationBuilder();
config.AddJsonFile("config.json");

var module = new ConfigurationModule(config.Build());

var builder = new ContainerBuilder();    
builder.RegisterModule(module);

var container = builder.Build();
------------------------

But I'm getting System.InvalidOperationException:

The type 'Implementations.ImplementationN, Implementations' could not be found. It may require assembly qualification, e.g. "MyType, MyAssembly"

I've uploaded my code to GitHub. Any help is appreciated.

P.S. Visual Studio 2019 16.4.5 Enterprise, Windows 10 1909 x64 Professional

UPDATE: To clarify more - my final goal is to have Interfaces.dll with no special references, Implementations.dll with reference to ONLY Interfaces.dll and Test.exe with reference to ONLY Interfaces.dll (and Autofac package of course). I expect Autofac to load specific class from a specific assembly (that are specified in config.json) via Reflection. That was possible with Unity Container and I expected to achieve the same with Autofac IoC.

Once again: I need an upproach without ANY project referencing Implementations.dll, I should be able to change specific implementation (via changing config.json) without recompilation.


Solution

  • I've found out this more a ".NET Core assembly loading" problem than an Autofac problem.

    To cut a long story short, if the assembly isn't specifically referenced by your app, you need to tell .NET Core assembly loader where to get it EVEN IF IT'S IN YOUR BIN FOLDER.

    So if you (like me) don't want to have a bootstrap library that references all implementation libraries that you need but you want to have runtime configuration via config file - you should do the following.

    From Autofac examples:

    // THIS IS THE MAGIC!
    // .NET Core assembly loading is confusing. Things that happen to be in your bin folder don't just suddenly
    // qualify with the assembly loader. If the assembly isn't specifically referenced by your app, you need to
    // tell .NET Core where to get it EVEN IF IT'S IN YOUR BIN FOLDER.
    // https://stackoverflow.com/questions/43918837/net-core-1-1-type-gettype-from-external-assembly-returns-null
    //
    // The documentation says that any .dll in the application base folder should work, but that doesn't seem
    // to be entirely true. You always have to set up additional handlers if you AREN'T referencing the plugin assembly.
    // https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/corehost.md
    //
    // To verify, try commenting this out and you'll see that the config system can't load the external plugin type.
    var executionFolder = Path.GetDirectoryName(typeof(Program).Assembly.Location);
    AssemblyLoadContext.Default.Resolving += (AssemblyLoadContext context, AssemblyName assembly) =>
    {
         // DISCLAIMER: NO PROMISES THIS IS SECURE. You may or may not want this strategy. It's up to
         // you to determine if allowing any assembly in the directory to be loaded is acceptable. This
         // is for demo purposes only.
         return context.LoadFromAssemblyPath(Path.Combine(executionFolder, $"{assembly.Name}.dll"));
     };
    

    And after that the following code:

     var config = new ConfigurationBuilder()
         .AddJsonFile("autofac.json")
         .Build();
     var configModule = new ConfigurationModule(config);
     var builder = new ContainerBuilder();
     builder.RegisterModule(configModule);
     var container = builder.Build();
    

    is working like a charm.

    P.S. Some more info from Microsoft