Search code examples
c#dependency-injectioninterfaceautofac

How can I resolve a class with 3rd party generic interface by string using autofac


There are different classes registered in autofac with same generic interface but using different class models. What I need is to resolve the class with string without knowing the class model. I am not sure if it can easily be achieved. Any suggestions please.

I am using a console app and I don't know the class model as we receive a string. I have registered all class in the start class but I want to resolve the class without knowing the class model. I don't want to use if else to say if string == "something" then resolve this else if resolve this. I am looking for the best practice.

public TestModel1 { }
public TestModel2 { }

public interface ICalculator<string, T> {}
public Impl1: ICalculator<string, TestModel1> {}
public Impl2: ICalculator<string, TestModel2> {}

in Autofac I register like:

builder.Register<Impl1>.As<ICalculator<string, TestModel1>>();
builder.Register<Impl2>.As<ICalculator<string, TestModel2>>();

in Console app I want to do something like: container.Resolve by string rather than doing like

if (...) {
class = container.resolve<ICalculator<string, TestModel1>>()
} else {
class = container.resolve<ICalculator<string, TestModel2>>()
}

I want to be able to do something like in console app:

var class = container.resolve<ICalculator>("pass string");

Solution

  • AFAIK You can only resolve what you register. SO you can't get an ICalculator from a ICalculator<string, TestModel2> because those are two very different interfaces.

    Use Named Services. You can either do:

    Assuming:

    public class Impl1 : ICalculator<string, TestModel1> {}
    public class Impl2 : ICalculator<string, TestModel1> {}
    // Notice these are the same ---------------------^
    // If these need to be different use #2 (Below)
    

    Register:

    builder.Register<Impl1>.Named<ICalculator<string, TestModel1>>("pass string1");
    builder.Register<Impl2>.Named<ICalculator<string, TestModel1>>("pass string2");
    // Notice these are the same ------------------------------^
    

    Resolved it via:

    var r = container.resolve<ICalculator<string, TestModel1>>("pass string2) 
    // = Impl2
    

    OR 2.

    First create a base interface:

    public interface ICalculator {}
    public interface ICalculator<T1, T2> : ICalcuator;
    
    public class Impl1 : ICalculator<string, TestModel1> {}
    public class Impl2 : ICalculator<string, TestModel2> {}
    

    Register:

    builder.Register<Impl1>.Named<ICalculator>("pass string1");
    builder.Register<Impl2>.Named<ICalculator>("pass string2");
    

    Resolved it via"

    var r = container.resolve<ICalculator>("pass string2) 
    // = Impl2
    

    You should really consider using scopes to help control the lifetime of your objects if your console app (or real app) is very complex:

    builder
      .Register<Impl1>
      .Named<ICalculator<string, TestModel1>>("pass string1")
      .InstancePerLifetimeScope();
    builder
      .Register<Impl2>
      .Named<ICalculator<string, TestModel1>>("pass string2")
      .InstancePerLifetimeScope();
    

    then

    using (scope1 = container.BeginLifetimeScope())
    {
      var r = scope1.Resolve<ICalculator>("pass string1");
      // r would be disposed if it implemented IDisposable
      // before scope1 is `Disposed()`
    }
    

    However, the problem I face is that the interface is coming from a library developed by another team.

    In that case you could try registering generic, and let the DI know the Generic types at resolution instead of during implementation:

    // @nuget: Autofac
    
    using System;
    using Autofac;
    
    public class Program
    {
        private static IContainer _Container;
        public static void Main()
        {
            var builder = new ContainerBuilder();
    
            builder
                .RegisterGeneric(typeof (Impl1<, >))
                .Named("pass string1", typeof (Calculator3rdParty<, >))
                .InstancePerLifetimeScope();
    
            builder
                .RegisterGeneric(typeof (Impl2<, >))
                .Named("pass string2", typeof (Calculator3rdParty<, >))
                .InstancePerLifetimeScope();
    
            _Container = builder.Build();
    
            using (var scope = _Container.BeginLifetimeScope())
            {
                var c3p = scope
                    .ResolveNamed<Calculator3rdParty<string, Model1>>("pass string2");
                // specify type at resolve ----------^
    
                Console.WriteLine(c3p.GetType());
            }
        }
    }
    
    public interface Calculator3rdParty<T1, T2>{}
    
    public class Model1{}
    
    public class Model2{}
    
    public class Impl1<T1, T2> : Calculator3rdParty<T1, T2>{}
    
    public class Impl2<T1, T2> : Calculator3rdParty<T1, T2>{}
    

    Output:

    Impl2`2[System.String,Model1]

    DotNetFiddle Example