1

I'm trying to figure out how to use multiple implementation of a base class with generics via dependency injection in .net core.

My base class is using generics so I can have different types of List in my response Dto.

I have successfully used many interface and base class implementations when there are no generics involved.

What I've tried so far.

Base class

public abstract class GeneratorBase<T>
{
    public abstract ProcessorResponse<T> Process();
}

Response dto

public class ProcessorResponse<T>
{
    public ProcessorResponse()
    {
        Data = new List<T>();
    }

    public List<T> Data { get; set; }
}

Implementation number 1

public class ConfigurationGenerator : GeneratorBase<ConfigurationModel>
{
    public override ProcessorResponse<ConfigurationModel> Process()
    {
        return new ProcessorResponse<ConfigurationModel>();
    }
}

Implementation number 2.

public class ApplicationGenerator : GeneratorBase<ApplicationModel>
{
    public override ProcessorResponse<ApplicationModel> Process()
    {
        return new ProcessorResponse<ApplicationModel>();
    }
}

Models

public class ConfigurationModel
{
    public int Count { get; set; }
}

public class ApplicationModel
{
    public string Title { get; set; }
}

My dependency injection to add the implementations.

public static void AddGenerators(this IServiceCollection services)
{
     // add our generators
     services.AddScoped<GeneratorBase<ConfigurationModel>, ConfigurationGenerator>();
     services.AddScoped<GeneratorBase<ApplicationModel>, ApplicationGenerator>();
}

Main App this is where my error is happening.

public class GeneratorApp
{

    // error because T is not implemented
    private readonly IEnumerable<GeneratorBase> _generators;

    // error because T is not implemented
    public GeneratorApp(IEnumerable<GeneratorBase> generators)
    {
        _generators = generators ?? throw new ArgumentException(nameof(generators));
    }

    public void RunGenerator(string name)
    {
        // get the generator by name and run process
        var generator = _generators.FirstOrDefault(c => c.GetType().Name == name);
        var results = generator.Process();
    }
}

Update IFoo Example IFoo example that works.

public interface IFoo
{
    string Name { get; }
}

public class Foo1 : IFoo
{
    public string Name => "I'm Foo 1";
}

public class Foo2 : IFoo
{
    public string Name => "I'm Foo 2";
}

Dependency injection to add the implementations.

public static void AddGenerators(this IServiceCollection services)
{
    // add our Foo's
    services.AddTransient<IFoo, Foo1>();
    services.AddTransient<IFoo, Foo2>();
}

Main App

public class GeneratorApp
{
    private IEnumerable<IFoo> _foos;

    public GeneratorApp(IEnumerable<IFoo> foos)
    {
        _foos = foos;
        RunGenerator("Foo1");
    }

    public void RunGenerator(string name)
    {
        foreach (var foo in _foos)
        {
            Console.WriteLine(foo.Name);
        }

        var foundFoo = _foos.FirstOrDefault(c => c.GetType().Name == name);
        if (foundFoo != null)
        {
            Console.WriteLine(foundFoo.Name);
        }
    }
}

Console output

I'm Foo 1

I'm Foo 2

I'm Foo 1

KC.
  • 87
  • 12
  • Sorry, Trimming done my code i missed that, its been updated. Never the less I still have the issue. – KC. Jan 22 '20 at 17:45
  • I've updated the code to be more clear. AddGenerators Is going to adding two different instances. – KC. Jan 22 '20 at 17:51
  • 1
    You are misusing the concept of dependency injection when you then want to decide which generator to use by passing in the actual class name of the concrete class you want to use. Could you explain why you think you should use di and why you think it is a good idea to use a concrete class name to select a generator. Both things actually contradict each other – NineBerry Jan 22 '20 at 18:00
  • In DI we can have many implementations of an Interface or even Base classes. I have many implementation of these Generators and I don't know which one the user will use until they pick one. I'm looking for suggestions.This all worked until I decide to implement a generic in a base class. – KC. Jan 22 '20 at 18:12
  • Maybe you can use service factory approach as suggested in this [answer](https://stackoverflow.com/questions/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core?answertab=active#tab-top)? – joostas Jan 22 '20 at 19:55
  • joostas I did see this post. I have no issues registering and resolving multiple implementations when my Interfaces or BaseClasses are not trying to do a generic. My issue i think is that I cant resolve List of implementations when using Generics. – KC. Jan 22 '20 at 20:34
  • 1
    @KC. "In DI we can have many implementations of an Interface or even Base classes." - yes, but those different implementations are meant to be selected at startup (or scope initialization) not at runtime or interactively. The different implementations are meant to be for Testing vs. Dev, or for A/B Research/Testing, and so on. It sounds like you just need the factory-pattern. – Dai Jan 22 '20 at 23:54

1 Answers1

2

The basics

You're misunderstanding the purpose (and correct usage) of dependency injection.

services.AddScoped<IFoo, Foo>();

To put it into words:

If you're creating a object whose constructor needs an IFoo, please insert a Foo instance.

That is the intention of dependency injection: to provide concrete objects even though they (the class' constructors) are asking for vague types.

It allows the classes to be vague, and thus not strongly depend on any particular implementation (= concrete classes).


Your problem

Very simply put, your constructor is asking a parameter type (IEnumerable<GeneratorBase>) that you never registered.

You only registered GeneratorBase<ConfigurationModel> and GeneratorBase<ApplicationModel>, which means that your dependency injection is only able to resolve constructor parameters of those two types. Anything else, the DI framework will throw an exception as it doesn't know how to fill it in.


The solution

It seems like you want a list of all (chosen) types to be injected. Therefore, you must register this exact type. For example:

services.AddScoped<IEnumerable<GeneratorBase>>(() => new List<GeneratorBase>()
{
    new ConfigurationGenerator(),
    new ApplicationGenerator()
});

This is just the shortest path to workable code. However, there are still further considerations, but your intention and use case simply isn't clear. I strongly suggest reading up on dependency injection as you are missing key knowledge on how to effectively leverage it.


Footnote: You did not post a definition for GeneratorBase (non-generic) but you did reference this type. I'm going to assume that this type exists and you forgot to add it to the question. If not, then there are also some misgivings about polymorphism with generics, which I also suggest you brush up on.

Flater
  • 12,908
  • 4
  • 39
  • 62
  • I guess I don't understand something. Say I have an IFoo interface that has Name as a property. I can create Foo1 :IFoo and Foo2:IFoo. Add them both to my serviceCollection. services.AddTransient(); services.AddTransient(); Then I can Inject an IEnumerable _foos in my GeneratorApp app class can get all instance of IFoo. I can use _foos.GetType().Name == "Foo1" and get the instances of Foo1, Do _foos.GetType().Name == "Foo2" and get an instance of Foo2. I would like to do the same thing with my GeneratorBase. See Update. – KC. Jan 23 '20 at 15:48
  • @KC. (1) _" Then I can Inject an IEnumerable _foos in my GeneratorApp app class can get all instance of IFoo."_ You can't do that, unless you explicitly registered `IEnumerable` in your DI framework, which means that you explicitly picked the concrete implementation of `IEnumerable` yourself. Your DI framework will not pick it for you. – Flater Jan 23 '20 at 15:49
  • @KC.: (2) `_foos.GetType().Name == "Foo2"` Is not valid code. I assume you mean `_foos.Where(foo => foo.GetType().Name == "Foo2")`. And yes, you can do this, but that's really not how you should be using dependency injection. You're creating an additional (sub) service provider (i.e. the IEnumerable you composed yourself) for no technical benefit other than doing it because you technically can do it. There's a big difference in being able to do something and it being a good idea. – Flater Jan 23 '20 at 15:52
  • Did you see my Updated example above? It works as describe without registering an IEnumerable.. That IEnumerable is constructed by the Dependency Framework I assume. – KC. Jan 23 '20 at 16:07
  • @KC.: Your updated code proves that you're essentially rolling your own service provider using the IEnumerable. Regardless of it working, it's not a good approach. This problem is also not related to dependency injection, as you would encounter the exact same issue even if you hadn't been using the DI container. As Dai already suggested, look up the factory pattern, because that is what you are looking for. And, in my opinion, look up ploymorphism in generics as you seem to struggle with seeing why you can't put varied generic implementations of a type into a single typed collection like that. – Flater Jan 23 '20 at 16:10