Dotnet Core Dependency injection, How to Register multiple instances of same class

Viewed 1722

I want to know if it is possible using build in dotnet core DI framework to register multiple instances of the same class

For example I have a class ToDo that sends off messages to clients. I want 2 instances of the same ToDo object with different injected configuration objects. So at the end I will have 2 separate instances of ToDo object.

Is this possible?


Edit:

Suppose I have a pattern pub/sub

I have a generic class called MessageSubService.cs and the implementation looks something like this

public class ASBMessageSubService : ASBSubService, IASBSubService
{
       public ASBMessageSubService(..., IOptions<ASBSubOptions> options): base(options)
}

So based on this I have multiple ASBMessageSubService that I will need to create. The only thing will differ is the IOptions that passed in. IOptions is internal access. I can not access that property if I use provider.GetRequireServices<T>.

I do understand I can do this

service.AddSingleton<ASBMessageSubService, IASBSubService>
service.AddSingleton<ASBMessageSubService, IASBSubService>
service.AddSingleton<ASBMessageSubService, IASBSubService>

This will register me 3 different instances. The issue is The implementation is that same and I will not be able to resolve it by the type where `nameof(ASBMessageSubService);

I can also register a deligate where I can resolve it based on name or type but this runs into same issue I described above, the type of implementation will be the same.

(I am aware that I can use libraries like structuremap or autofac to get this done with registering them as named instance. However I would like to avoid 3rd party tools like this in this project. )

Any suggestions on this?

Thank you!

3 Answers

David G answered it. I'll expand it slightly to help you and others.

You can register multiple classes, either as themselves, or as an interface:

e.g.

services.AddTransient<Todo>(provider => new Todo(configuration1));
services.AddTransient<Todo>(provider => new Todo(configuration2));
services.AddTransient<Todo>(provider => new Todo(configuration3));
...
services.AddTransient<ITodoWorker, NeedTodos>();

And then for dependency injection take a dependency on IEnumerable<Todo>:

public class NeedTodos : ITodoWorker
{
    public NeedTodos(IEnumerable<Todo> todos)
    {
        foreach (var todo in todos)
        {
            if (todo.Id == "configuration1")
            {
                // an idea if you need to find a specific Todo instance
            }
        }
    }
}

This declaration is probably around the wrong way, if that's not just an editing mistake:

service.AddSingleton<ASBMessageSubService, IASBSubService>

should most likely be:

service.AddSingleton<IASBSubService, ASBMessageSubService>

Then your class take those as multiple dependences, like this:

public class Subscribers
{
    public Subscribers(IEnumerable<IASBSubService> subs)
    {
        ...
    }
}

As for finding a specific instance, what is in ASBSubOptions that will differentiate an IASBSubService?

Let me guess and say it's a property Name. A subscriber might have a name. In that case:

public interface IASBSubService
{
    string Name { get; }
    ...
}

then:

public class ASBMessageSubService : IASBSubService, ...
{
    public string Name { get; private set; }

    public ASBMessageSubService(..., IOptions<ASBSubOptions> options)
    {
        Name = options.Value.SubscriberName;
        ...
    }
}

Now if we go back to the Subscribers class I invented, assuming you have something like this, then you can find an instance by Name:

public class Subscribers
{
    public Subscribers(IEnumerable<IASBSubService> subs)
    {
        var sub = subs.SingleOrDefault(x => x.Name == "Service1");
    }
}

If it's the concrete type you are after, whether it's an ASBSubService or ASBMessageSubService, you can get the concrete type using:

var sub = subs.SingleOrDefault(x => x.Name.GetType() == typeof(ASBSubService));

// GetType() never returns the interface type (I think) See https://stackoverflow.com/questions/1159906/finding-the-concrete-type-behind-an-interface-instance/1159940

Exposing something in the interface to help you choose the type of instance is probably what you are looking for. That's a very common approach to this type of problem.

This has stretched me a little. I've had to pop the hood on the DI mechanics:

First, the interfaces and classes:

public interface ISubService {} // just a second interface I made up

public interface IASBSubService {}

public class ASBMessageSubService : IASBSubService, ISubService
{
   public string Name { get; set; }

   public ASBMessageSubService(string name)
   {
       Name = name;
   }
}

Now, I found a way to re-register existing registered instances as a different interface:

services.AddSingleton<ASBMessageSubService>(provider => new SubServiceSubscriber("sub1"));
services.AddSingleton<ASBMessageSubService>(provider => new SubServiceSubscriber("sub2"));
services.AddSingleton<ASBMessageSubService>(provider => new SubServiceSubscriber("sub3"));

// re-register as IASBSubService
foreach (var service in services.Where(s => s.ServiceType == typeof(ASBMessageSubService)).ToList())
{
    services.AddSingleton<IASBSubService >(provider => service.ImplementationFactory(provider) as IASBSubService );
}

// re-register as ISubService
foreach (var service in services.Where(s => s.ServiceType == typeof(ASBMessageSubService)).ToList())
{
    services.AddSingleton<ISubService>(provider => service.ImplementationFactory(provider) as ISubService);
}

And then take a dependency on the interface you need:

public class Subscriptions
{
    public Subscriptions(IEnumerable<IASBSubService> subs)
    { ... }
}

or

public class ServiceManager
{
    public ServiceManager(IEnumerable<ISubService> subs)
    { ... }
}

If there's a cleaner way, someone else will need to show me.

Related