I have services that are derived from the same interface.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService
Most of the answers here violate the single responsibility principle (a service class should not resolve dependencies itself) and/or use the service locator anti-pattern.
Another option to avoid these problems is to:
I’ve written an article with more details: Dependency Injection in .NET: A way to work around missing named registrations
I have run into the same problem and I worked with a simple extension to allow Named services. You can find it here:
It allows you to add as many (named) services as you want like this:
var serviceCollection = new ServiceCollection();
serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), "A", ServiceLifetime.Transient);
serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), "B", ServiceLifetime.Transient);
var serviceProvider = serviceCollection.BuildServiceProvider();
var myServiceA = serviceProvider.GetService<IMyService>("A");
var myServiceB = serviceProvider.GetService<IMyService>("B");
The library also allows you to easy implement a "factory pattern" like this:
[Test]
public void FactoryPatternTest()
{
var serviceCollection = new ServiceCollection();
serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), MyEnum.A.GetName(), ServiceLifetime.Transient);
serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), MyEnum.B.GetName(), ServiceLifetime.Transient);
serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var factoryPatternResolver = serviceProvider.GetService<IMyServiceFactoryPatternResolver>();
var myServiceA = factoryPatternResolver.Resolve(MyEnum.A);
Assert.NotNull(myServiceA);
Assert.IsInstanceOf<MyServiceA>(myServiceA);
var myServiceB = factoryPatternResolver.Resolve(MyEnum.B);
Assert.NotNull(myServiceB);
Assert.IsInstanceOf<MyServiceB>(myServiceB);
}
public interface IMyServiceFactoryPatternResolver : IFactoryPatternResolver<IMyService, MyEnum>
{
}
public class MyServiceFactoryPatternResolver : FactoryPatternResolver<IMyService, MyEnum>, IMyServiceFactoryPatternResolver
{
public MyServiceFactoryPatternResolver(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
}
public enum MyEnum
{
A = 1,
B = 2
}
Hope it helps
You're correct, the built in ASP.NET Core container does not have the concept of registering multiple services and then retrieving a specific one, as you suggest, a factory is the only real solution in that case.
Alternatively, you could switch to a third party container like Unity or StructureMap that does provide the solution you need (documented here: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-the-default-services-container).
Extending the solution of @rnrneverdies. Instead of ToString(), following options can also be used- 1) With common property implementation, 2) A service of services suggested by @Craig Brunetti.
public interface IService { }
public class ServiceA : IService
{
public override string ToString()
{
return "A";
}
}
public class ServiceB : IService
{
public override string ToString()
{
return "B";
}
}
/// <summary>
/// extension method that compares with ToString value of an object and returns an object if found
/// </summary>
public static class ServiceProviderServiceExtensions
{
public static T GetService<T>(this IServiceProvider provider, string identifier)
{
var services = provider.GetServices<T>();
var service = services.FirstOrDefault(o => o.ToString() == identifier);
return service;
}
}
public void ConfigureServices(IServiceCollection services)
{
//Initials configurations....
services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();
var sp = services.BuildServiceProvider();
var a = sp.GetService<IService>("A"); //returns instance of ServiceA
var b = sp.GetService<IService>("B"); //returns instance of ServiceB
//Remaining configurations....
}
I just simply inject an IEnumerable
ConfigureServices in Startup.cs
Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
{
services.AddScoped(typeof(IService), t);
});
Services Folder
public interface IService
{
string Name { get; set; }
}
public class ServiceA : IService
{
public string Name { get { return "A"; } }
}
public class ServiceB : IService
{
public string Name { get { return "B"; } }
}
public class ServiceC : IService
{
public string Name { get { return "C"; } }
}
MyController.cs
public class MyController
{
private readonly IEnumerable<IService> _services;
public MyController(IEnumerable<IService> services)
{
_services = services;
}
public void DoSomething()
{
var service = _services.Where(s => s.Name == "A").Single();
}
...
}
Extensions.cs
public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
{
return assembly.GetTypesAssignableFrom(typeof(T));
}
public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
{
List<Type> ret = new List<Type>();
foreach (var type in assembly.DefinedTypes)
{
if (compareType.IsAssignableFrom(type) && compareType != type)
{
ret.Add(type);
}
}
return ret;
}
After reading the answers here and articles elsewhere I was able to get it working without strings. When you have multiple implementations of the same interface the DI will add these to a collection, so it's then possible to retrieve the version you want from the collection using typeof
.
// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(IService, ServiceA);
services.AddScoped(IService, ServiceB);
services.AddScoped(IService, ServiceC);
}
// Any class that uses the service(s)
public class Consumer
{
private readonly IEnumerable<IService> _myServices;
public Consumer(IEnumerable<IService> myServices)
{
_myServices = myServices;
}
public UseServiceA()
{
var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
serviceA.DoTheThing();
}
public UseServiceB()
{
var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
serviceB.DoTheThing();
}
public UseServiceC()
{
var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
serviceC.DoTheThing();
}
}