Misha Sloushch
Misha Sloushch

Reputation: 21

Autofac - resolve Generic Type through Controller constructor parameter

I have abstract generic class

 public abstract class AbstractLogic<T> {}

And two implementations

DefaultLogic  : AbstractLogic<ClassA>{}
SpecificLogic : AbstractLogic<ClassB>{}

As we see, if I want to create instance of oone of them I can easily do it without specifing open generic class.

DefaultLogic logic= new DefaultLogic();

Now I need to create instance of each of them with the help of DI. So, I registered types like that

    var logicList= new[]
        {
            Assembly.GetAssembly(typeof(DefaultLogic)),
            Assembly.GetAssembly(typeof(SpecificLogic))
        };
        builder.RegisterAssemblyTypes(logicList).AsClosedTypesOf(typeof(AbstractLogic<>))
            .Where(t => t.Name.StartsWith(Settings.GetCurrentMode().ToString()));

Settings.GetCurrentMode() - returns me a name of instance that I need (Default of Specific).

No I want to Inject service into controller to make it able to load needed logic service.

public class ListController : Controller
{
    private AbstractLogic<???> _logic;
    public ListController(AbstractLogic<???> logic)
        {
            _logic = logic;
        }

}

Compiler asks me to define model instead of ???. But, I don't need it, since Implementation ob abstract class already chose an open generic type over inheritance. Is there other way how can I resolve needed logic with autofac?

Upvotes: 0

Views: 834

Answers (1)

Alexander Leonov
Alexander Leonov

Reputation: 4794

In my opinion you have three more or less real options here.

1) Make controller generic and then pass controller's type parameter to the injected type itself (see below). This way controller would be aware of the particular type T and would be able to use it without any weird dances. However, next question is - how to explain all this stuff to a framework that will be resolving such a controller? DotNet Core by default does not support generic controllers. Luckily, it is quite possible to do such a trick. You can read some details about it in this answer and further by the link to Microsoft's documentation. It's not simple though, so this can be overkill for your task... I'm just not sure what your task really is.

[Route("api/[controller]")]
public class ValuesController<T> : Controller where T: class, new()
{
    private readonly AbstractLogic<T> _logic;

    public ValuesController(AbstractLogic<T> logic)
    {
        _logic = logic;
    }

2) Use reflection to resolve the AbstractLogic<T>. First of all, you need to declare private field to store the reference to AbstractLogic<T>. The only way how it can be done is described here. After that you need to resolve it using the particular type, and the only way to do it is construct this type at runtime using reflection. Such things tend to become pretty cumbersome pretty quickly.

3) Most reasonable option in my opinion. You need to introduce base non-generic type for AbstractLogic, inherit AbstractLogic<T> from it, register particular generic implementation in the container as non-generic AbstractLogic, and then just inject non-generic AbstractLogic into your controller. This way you'll need to move generic-aware logic to a generic classes while moving essential component interface to non-generic parent AbstractLogic. However, this adds a constraint: the main component non-generic interface must not depend on particular generic type parameter of child class in any way. Since I have no idea about the rest of your code I cannot say if this is possible in your case or not. General idea of the code is below.

// class hierarchy
public abstract class AbstractLogic
{
    public string DoStufF()
    {
        return DoStufFInternal();
    }

    protected abstract string DoStufFInternal();
}

// Here 'where' constraint is not essential, I'm just lazy enough
// and implemented property setting in the dumbest possible way which required the constraint :)
public abstract class AbstractLogic<T> : AbstractLogic where T: class, new()
{
    protected AbstractLogic()
    {
        SomeProperty = new T();
    }

    public T SomeProperty { get; private set; }
}

public class DefaultLogic : AbstractLogic<ClassA>
{
    protected override string DoStufFInternal()
    {
        return $"Default stuff: SomeProperty = {SomeProperty.ToString()}";
    }
}

public class SpecificLogic : AbstractLogic<ClassB>
{
    protected override string DoStufFInternal()
    {
        return $"Specific stuff: SomeProperty = {SomeProperty.ToString()}";
    }
}

public class ClassA
{
    public override string ToString()
    {
        return "Class A representation";
    }
}

public class ClassB
{
    public override string ToString()
    {
        return "Class B representation";
    }
}

// registering class
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
    .Where(t => t.Name.StartsWith(Settings.GetCurrentMode().ToString()))
    .As<AbstractLogic>()
    .InstancePerLifetimeScope();

// and using it in the controller
[Route("api/[controller]")]
public class DITestController : Controller
{
    private readonly AbstractLogic _logic;

    public DITestController(AbstractLogic logic)
    {
        _logic = logic;
    }

    [HttpGet]
    public IActionResult Get()
    {
        return Ok(_logic.DoStufF());
    }
}

Upvotes: 1

Related Questions