I have services that are derived from the same interface.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService
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 {
void DoServiceOperation();
}
public class MongoService : IService {
private readonly MongoDataSource _options;
public FooService(IOptionsMonitor serviceOptions){
_options = serviceOptions.CurrentValue
}
void DoServiceOperation(){
//do something with your mongo data source options (connect to database)
throw new NotImplementedException();
}
}
public class SqlService : IService {
private readonly SqlDataSource_options;
public SqlService (IOptionsMonitor 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(configurationSection.GetSection("sqlDataSource"));
services.Configure(configurationSection.GetSection("mongoDataSource"));
services.AddTransient, SqlService>();
services.AddTransient, 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 _mongoService;
private readonly IService _sqlService ;
public class ControllerWhichNeedsMongoService(
IService mongoService,
IService 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.