Search code examples
autofacautofac-configuration

Two implementations of the same interface in Autofac's JSON configuration


I need to enable late-binding in an application and I want to have an option to explicitly configure services using JSON configuration file.

I have an interface IDependency and two classes DependencyOne and DependencyTwo which both implement the interface. I also have a SomeService class that has a constructor with following signature:

public SomeService(IDependency dependency1, IDependency dependency2)

I want to inject DependencyOne for dependency1, and DependencyTwo for dependency2.

How can I configure that purely in JSON configuration, without using any attributes in the code? Is it possible at all?

The no-attribute requirement is needed because the late-bound assembly is not supposed to depend on AutoFac.

Thanks in advance.

Update: solution

Travis' answer below contains a link to FAQ which led me to acceptable solution. Use "marker" interfaces, e.g. IDependencyOne : IDependency and IDependencyTwo : IDependency, and then SomeService(IDependencyOne dependency1, IDependencyTwo dependency2). The thing I don't like so much is that now a generic decorator for IDependency needs to implement all markers, say LoggingDecorator : IDependencyOne, IDependencyTwo if I want to use it in SomeService, but as long as the markers stay empty, that's not a big problem. This way I don't have to enforce dependency on Autofac's dlls in the late-bound assembly, while still have DI configured in JSON file.


Solution

  • If you have two different implementations of the same interface that can't be treated the same, that's a code smell. There is an FAQ about ways to work around that but the short answer is you won't literally be able to specify two different instances of the same interface like this without some manual work. There isn't a mechanism for easily wiring this up because it's a design problem.

    Take a moment to read the FAQ to see why and additional ideas for how to solve this beyond just what I'm showing here.

    However, let's assume you can't change the interface IDependency since usually that's the sticking point for folks. Let's also assume you can't just put DependencyOne and DependencyTwo right in the constructor for... whatever reason. (Both of those would be the first place I'd look to resolve this rather than trying to complicate my DI wire-up, but again, let's say for the sake of argument that's not an option.)

    I would probably register each instance in config with a metadata key that can be used later.

    {
      "components": [{
        "type": "MyAssembly.DependencyOne, MyAssembly",
        "services": [{
          "type": "MyAssembly.IDependency, MyAssembly"
        }],
        "metadata": [{
          "key": "type",
          "value": "One",
          "type": "System.String, mscorlib"
        }]
      }, {
        "type": "MyAssembly.DependencyTwo, MyAssembly",
        "services": [{
          "type": "MyAssembly.IDependency, MyAssembly"
        }],
        "metadata": [{
          "key": "type",
          "value": "Two",
          "type": "System.String, mscorlib"
        }]
      }]
    }
    

    OK, so we have two components that each expose the same interface, and each has a metadata key of One or Two respectively.

    In your class you can use that metadata.

    public class SomeService
    {
      readonly IEnumerable<Meta<IDependency>> _dependencies;
    
      public SomeService(IEnumerable<Meta<IDependency>> dependencies)
      {
        _dependencies = dependencies;
      }
    
      public void DoSomething(string parameter)
      {
        var dep = _dependencies.First(a => a.Metadata["type"].Equals(parameter));
        dep.DoSomething();
      }
    }
    

    The idea is that you can choose the appropriate thing using the metadata. Obviously adapt that to your own needs; maybe it's not a parameter from the caller but something in your code elsewhere; the concept still holds.

    Again, though, I can't recommend strongly enough that you check out the FAQ and strongly consider a redesign to avoid this situation entirely. It will make your life and the lives of your fellow developers easier in the long run.