I have services that are derived from the same interface.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService
Apparently, you can just inject IEnumerable of your service interface! And then find the instance that you want using LINQ.
My example is for the AWS SNS service but you can do the same for any injected service really.
Startup
foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
services.AddAWSService<IAmazonSimpleNotificationService>(
string.IsNullOrEmpty(snsRegion) ? null :
new AWSOptions()
{
Region = RegionEndpoint.GetBySystemName(snsRegion)
}
);
}
services.AddSingleton<ISNSFactory, SNSFactory>();
services.Configure<SNSConfig>(Configuration);
SNSConfig
public class SNSConfig
{
public string SNSDefaultRegion { get; set; }
public string SNSSMSRegion { get; set; }
}
appsettings.json
"SNSRegions": "ap-south-1,us-west-2",
"SNSDefaultRegion": "ap-south-1",
"SNSSMSRegion": "us-west-2",
SNS Factory
public class SNSFactory : ISNSFactory
{
private readonly SNSConfig _snsConfig;
private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;
public SNSFactory(
IOptions<SNSConfig> snsConfig,
IEnumerable<IAmazonSimpleNotificationService> snsServices
)
{
_snsConfig = snsConfig.Value;
_snsServices = snsServices;
}
public IAmazonSimpleNotificationService ForDefault()
{
return GetSNS(_snsConfig.SNSDefaultRegion);
}
public IAmazonSimpleNotificationService ForSMS()
{
return GetSNS(_snsConfig.SNSSMSRegion);
}
private IAmazonSimpleNotificationService GetSNS(string region)
{
return GetSNS(RegionEndpoint.GetBySystemName(region));
}
private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
{
IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);
if (service == null)
{
throw new Exception($"No SNS service registered for region: {region}");
}
return service;
}
}
public interface ISNSFactory
{
IAmazonSimpleNotificationService ForDefault();
IAmazonSimpleNotificationService ForSMS();
}
Now you can get the SNS service for the region that you want in your custom service or controller
public class SmsSender : ISmsSender
{
private readonly IAmazonSimpleNotificationService _sns;
public SmsSender(ISNSFactory snsFactory)
{
_sns = snsFactory.ForSMS();
}
.......
}
public class DeviceController : Controller
{
private readonly IAmazonSimpleNotificationService _sns;
public DeviceController(ISNSFactory snsFactory)
{
_sns = snsFactory.ForDefault();
}
.........
}
A factory approach is certainly viable. Another approach is to use inheritance to create individual interfaces that inherit from IService, implement the inherited interfaces in your IService implementations, and register the inherited interfaces rather than the base. Whether adding an inheritance hierarchy or factories is the "right" pattern all depends on who you speak to. I often have to use this pattern when dealing with multiple database providers in the same application that uses a generic, such as IRepository<T>
, as the foundation for data access.
Example interfaces and implementations:
public interface IService
{
}
public interface IServiceA: IService
{}
public interface IServiceB: IService
{}
public IServiceC: IService
{}
public class ServiceA: IServiceA
{}
public class ServiceB: IServiceB
{}
public class ServiceC: IServiceC
{}
Container:
container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
FooA
, FooB
and FooC
implements IFoo
Services Provider:
services.AddTransient<FooA>(); // Note that there is no interface
services.AddTransient<FooB>();
services.AddTransient<FooC>();
services.AddSingleton<Func<Type, IFoo>>(x => type =>
{
return (IFoo)x.GetService(type);
});
Destination:
public class Test
{
private readonly IFoo foo;
public Test(Func<Type, IFoo> fooFactory)
{
foo = fooFactory(typeof(FooA));
}
....
}
If you want to change the FooA
to FooAMock
for test purposes:
services.AddTransient<FooAMock>();
services.AddSingleton<Func<Type, IFoo>>(x => type =>
{
if(type.Equals(typeof(FooA))
return (IFoo)x.GetService(typeof(FooAMock));
return null;
});
I did a simple workaround using Func
when I found myself in this situation.
Firstly declare a shared delegate:
public delegate IService ServiceResolver(string key);
Then in your Startup.cs
, setup the multiple concrete registrations and a manual mapping of those types:
services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();
services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
switch (key)
{
case "A":
return serviceProvider.GetService<ServiceA>();
case "B":
return serviceProvider.GetService<ServiceB>();
case "C":
return serviceProvider.GetService<ServiceC>();
default:
throw new KeyNotFoundException(); // or maybe return null, up to you
}
});
And use it from any class registered with DI:
public class Consumer
{
private readonly IService _aService;
public Consumer(ServiceResolver serviceAccessor)
{
_aService = serviceAccessor("A");
}
public void UseServiceA()
{
_aService.DoTheThing();
}
}
Keep in mind that in this example the key for resolution is a string, for the sake of simplicity and because OP was asking for this case in particular.
But you could use any custom resolution type as key, as you do not usually want a huge n-case switch rotting your code. Depends on how your app scales.
My solution for what it's worth... considered switching to Castle Windsor as can't say I liked any of the solutions above. Sorry!!
public interface IStage<out T> : IStage { }
public interface IStage {
void DoSomething();
}
Create your various implementations
public class YourClassA : IStage<YouClassA> {
public void DoSomething()
{
...TODO
}
}
public class YourClassB : IStage<YourClassB> { .....etc. }
Registration
services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()
Constructor and instance usage...
public class Whatever
{
private IStage ClassA { get; }
public Whatever(IStage<YourClassA> yourClassA)
{
ClassA = yourClassA;
}
public void SomeWhateverMethod()
{
ClassA.DoSomething();
.....
}
I know this post is a couple years old, but I keep running into this and I'm not happy with the service locator pattern.
Also, I know the OP is looking for an implementation which allows you to choose a concrete implementation based on a string. I also realize that the OP is specifically asking for an implementation of an identical interface. The solution I'm about to describe relies on adding a generic type parameter to your interface. The problem is that you don't have any real use for the type parameter other than service collection binding. I'll try to describe a situation which might require something like this.
Imagine configuration for such a scenario in appsettings.json which might look something like this (this is just for demonstration, your configuration can come from wherever you want as long as you have the correction configuration provider):
{
"sqlDataSource": {
"connectionString": "Data Source=localhost; Initial catalog=Foo; Connection Timeout=5; Encrypt=True;",
"username": "foo",
"password": "this normally comes from a secure source, but putting here for demonstration purposes"
},
"mongoDataSource": {
"hostName": "uw1-mngo01-cl08.company.net",
"port": 27026,
"collection": "foo"
}
}
You really need a type that represents each of your configuration options:
public class SqlDataSource
{
public string ConnectionString { get;set; }
public string Username { get;set; }
public string Password { get;set; }
}
public class MongoDataSource
{
public string HostName { get;set; }
public string Port { get;set; }
public string Collection { get;set; }
}
Now, I know that it might seem a little contrived to have two implementations of the same interface, but it I've definitely seen it in more than one case. The ones I usually come across are:
Anyway, you can reference them by adding a type parameter to your service interface so that you can implement the different implementations:
public interface IService<T> {
void DoServiceOperation();
}
public class MongoService : IService<MongoDataSource> {
private readonly MongoDataSource _options;
public FooService(IOptionsMonitor<MongoDataSource> serviceOptions){
_options = serviceOptions.CurrentValue
}
void DoServiceOperation(){
//do something with your mongo data source options (connect to database)
throw new NotImplementedException();
}
}
public class SqlService : IService<SqlDataSource> {
private readonly SqlDataSource_options;
public SqlService (IOptionsMonitor<SqlDataSource> serviceOptions){
_options = serviceOptions.CurrentValue
}
void DoServiceOperation(){
//do something with your sql data source options (connect to database)
throw new NotImplementedException();
}
}
In startup, you'd register these with the following code:
services.Configure<SqlDataSource>(configurationSection.GetSection("sqlDataSource"));
services.Configure<MongoDataSource>(configurationSection.GetSection("mongoDataSource"));
services.AddTransient<IService<SqlDataSource>, SqlService>();
services.AddTransient<IService<MongoDataSource>, MongoService>();
Finally in the class which relies on the Service with a different connection, you just take a dependency on the service you need and the DI framework will take care of the rest:
[Route("api/v1)]
[ApiController]
public class ControllerWhichNeedsMongoService {
private readonly IService<MongoDataSource> _mongoService;
private readonly IService<SqlDataSource> _sqlService ;
public class ControllerWhichNeedsMongoService(
IService<MongoDataSource> mongoService,
IService<SqlDataSource> sqlService
)
{
_mongoService = mongoService;
_sqlService = sqlService;
}
[HttpGet]
[Route("demo")]
public async Task GetStuff()
{
if(useMongo)
{
await _mongoService.DoServiceOperation();
}
await _sqlService.DoServiceOperation();
}
}
These implementations can even take a dependency on each other. The other big benefit is that you get compile-time binding so any refactoring tools will work correctly.
Hope this helps someone in the future.