nak
nak

Reputation: 936

How to get different instances of same service from IServiceCollection

I have a Bar class which is defined as this

public class Bar: IBar
{
    private readonly IFoo _foo;
    private readonly string _keyname;

    public Bar(IFoo, string keyName)
    {
        //assign class field
    }
}

The keyname string differentiates the object of Bar. The methods of Bar use this keyname to fetch different settings/config from outside the class.

I want the client of the class to have different instances of Bar in a single class. If we do not use Services the client can do something like this

var bar1 = new Bar(foo, "key1");//instantiate with new
var bar2 = new Bar(foo, "key2");

To register the Bar class, I have created an extension method like this

public static IServiceCollection AddMyService(this IServiceCollection services, string keyName)
{
    services.AddSingleton<IFoo, Foo>();
    services.AddTransient<IBar>(sp => 
       ActivatorUtilities.CreateInstance<Bar>(sp, keyName)
    );
    return services;
}

I want the client to register multiple instances like this

services.AddMyService("key1");
services.AddMyService("key2");

But I am not sure how will they access both the registered services in their class through dependency injection. Is it the correct way to go about and solve this problem?

Upvotes: 1

Views: 1999

Answers (2)

Good Night Nerd Pride
Good Night Nerd Pride

Reputation: 8500

You could solve this by injecting a factory that creates an IBar for a given key:

public interface IBarFactory { IBar Create(string key); }

public class BarFactory : IBarFactory {
    private readonly IFoo _foo;
    public BarFactory(IFoo foo) => _foo = foo;
    public IBar Create(string key) => new Bar(_foo, key);
}

Client code can inject this factory:

public class Client1 {
    private readonly IBar _bar1;
    public Client1(IBarFactory barFactory)
        => _bar1 = barFactory.Create("key1");
}

Note that the BarFactory needs to inject and forward all the dependencies of Bar.

Only IBarFactory needs to be registered in your DI container. No registrations are needed for the different IBars (except their dependencies like IFoo, of course).

One disadvantage is the extra setup that is required when you want mock the IBar in unit tests of Client1. You'd have to mock & setup IBarFactory to return a mock of IBar.

Upvotes: 1

ddfra
ddfra

Reputation: 2575

The dependency injection that comes out of the box with dotnet core doesn't allow to use named services.

The easiest thing you can do, in your case is to register all the Bar services and then inject them as an IEnumerable:

 var factory = ActivationUtilities.CreateFactory(typeof(Bar), new Type[] { typeof(string) });
 services.AddSingleton<IFoo, Foo>();
 services.AddTransient<IBar>(sp => 
   (IBar) factory(sp, new object[] { "key1" })
 );
 
 services.AddTransient<IBar>(sp => 
   (IBar) factory(sp, new object[] { "key2" })
 );
 services.AddTransient<AClient>();

 public class AClient {
      private IEnumerable<IBar> _bars;
      public AClient(IEnumerable<IBar> bars)
      {
          _bars = bars
      }

      public IBar GetBarByKey(string key) => _bars.FirstOrDefault(x => x.Key == key);
 }

The ActivationUtilities.CreateFactory method returns an object of ObjectFactory delegate, which represents a function which takes in input an IServiceProvider and a list of arguments. In this way you can create objects taking some constructions arguments from the service provider while some arguments are given directly.

Upvotes: 1

Related Questions