5

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!

kkdeveloper7
  • 497
  • 2
  • 11
  • 25
  • 2
    You call `services.AddSingleton(...)` multiple times and your consumer classes can take an `IEnumerable` for example. – DavidG Jun 08 '21 at 21:30
  • @DavidG Thank you. I am aware of this approach. However, My services are singletons and what I am confused from this example is that once I inject `IEnumnerable<>` How will I know what instance to get, since the underneath implementations is that same? For example I will register same `ToDoService` multiple times per `IToDo` interface. ? – kkdeveloper7 Jun 09 '21 at 00:11

3 Answers3

2

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
            }
        }
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jevon Kendon
  • 545
  • 4
  • 13
  • If you need more elaborate DI, definitely check out Scrutor (https://github.com/khellang/Scrutor) – Jevon Kendon Jun 08 '21 at 23:23
  • Thank you, the issue with this solution is that `todo.Id` will not be accessible since it will be provided by DI as well and will be `private readonly` at each implementation. I think what I will need is a `NamedInstance` to be able to resolve services by name – kkdeveloper7 Jun 09 '21 at 14:09
  • OK. You would have to provide a real example then if you want further help. I've only ever needed to use the `Id` like approach above, or I've only needed to just process every `Todo` (i.e. not single out an instance) – Jevon Kendon Jun 09 '21 at 21:33
  • Update the question. – kkdeveloper7 Jun 10 '21 at 14:08
0

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.

Jevon Kendon
  • 545
  • 4
  • 13
  • If not, find me on Discord. – Jevon Kendon Jun 10 '21 at 22:12
  • Of course the interface doesn't have to be `Name`. It can be any kind of discriminator you choose. You could return `Type` and expose the underlying type that way. Exposing something in the interface to help you choose the type of instance your after might be what you are looking for. – Jevon Kendon Jun 10 '21 at 22:19
  • Thank you. Again this will have me expose unwanted properties just to resolve dependencies. If Other member of the team will be consuming this API, I do not think this will scale well, since they would have to provide name for each implementation. Thank you for the solution and the idea though.. – kkdeveloper7 Jun 13 '21 at 00:30
  • You need a discriminator. What will your discriminator be? Will it be the type of the implementing class? – Jevon Kendon Jun 13 '21 at 02:28
  • 1
    I was looking for something among these lines: https://autofac.readthedocs.io/en/latest/advanced/keyed-services.html this allows you to register you a named instance and be able to resolve it by name at resolution time. I am not sure if there is something like this I can do in dotnet DI framework out of the box. – kkdeveloper7 Jun 14 '21 at 13:14
  • I posted a new answer. – Jevon Kendon Jun 14 '21 at 21:44
  • Ah. I understand you better. I'll need to think about it. – Jevon Kendon Jun 14 '21 at 21:50
0

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.

Jevon Kendon
  • 545
  • 4
  • 13