I have services that are derived from same interface
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
Typically other IOC containers like Unity
allow you to register concrete implementations by some Key
that distinguishes them.
In Asp.Net Core how do I register these services and resolve it at runtime based on some key?
I don't see any of the Add
Service method takes key
or name
parameter that typically used to distinguish the concrete implementation.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services here of the same interface
}
public MyController:Controller
{
public void DoSomeThing(string key)
{
// How do get service based on key
}
}
Is the Factory pattern the only option here?
Update1
I have gone though the article here that shows how to use factory pattern to get service instances when we have multiple concreate implementation. However it is still not complete solution. when I call _serviceProvider.GetService()
method I cannot inject data into constructor. For example consider this example
public class ServiceA : IService
{
private string _efConnectionString;
ServiceA(string efconnectionString)
{
_efConnecttionString = efConnectionString;
}
}
public class ServiceB : IService
{
private string _mongoConnectionString;
public ServiceB(string mongoConnectionString)
{
_mongoConnectionString = mongoConnectionString;
}
}
public class ServiceC : IService
{
private string _someOtherConnectionString
public ServiceC(string someOtherConnectionString)
{
_someOtherConnectionString = someOtherConnectionString;
}
}
How can _serviceProvider.GetService()
inject appropriate connection string?
In Unity or any other IOC we can do that at the time of type registration. I can use IOption however that will require me to inject all settings, I cannot inject a particular connectionstring into the service.
Also note that I am trying to avoid using other containers (including Unity) because then I have to register everything else ( eg Controllers) with new container as well.
Also using factory pattern to create service instance is against DIP as factory increases the number of dependencies a client is forced to depend upon details here
So I think the default DI in ASP.NET core missing 2 things
1>Register instances using key
2>Inject static data into constructor during registration
How about a service for services?
If we had an INamedService interface (with .Name property), we could write an IServiceCollection extension for .GetService(string name), where the extension would take that string parameter, and do a .GetServices() on itself, and in each returned instance, find the instance whose INamedService.Name matches the given name.
Like this:
Therefore, your IMyService must implement INamedService, but you'll get the key-based resolution you want, right?
To be fair, having to even have this INamedService interface seems ugly, but if you wanted to go further and make things more elegant, then a [NamedServiceAttribute("A")] on the implementation/class could be found by the code in this extension, and it'd work just as well. To be even more fair, Reflection is slow, so an optimization may be in order, but honestly that's something the DI engine should've been helping with. Speed and simplicity are each grand contributors to TCO.
All in all, there's no need for an explicit factory, because "finding a named service" is such a reusable concept, and factory classes don't scale as a solution. And a Func<> seems fine, but a switch block is so bleh, and again, you'll be writing Funcs as often as you'd be writing Factories. Start simple, reusable, with less code, and if that turns out not to do it for ya, then go complex.
Bit late to this party, but here is my solution:...
Startup.cs or Program.cs if Generic Handler...
IMyInterface of T Interface Setup
Concrete implementations of IMyInterface of T
Hopefully if there is any issue with doing it this way, someone will kindly point out why this is the wrong way to do this.
While the out of the box implementation doesn't offer it, here's a sample project that allows you to register named instances, and then inject INamedServiceFactory into your code and pull out instances by name. Unlike other facory solutions here, it will allow you to register multiple instances of same implementation but configured differently
https://github.com/macsux/DotNetDINamedInstances
I did something similar sometime ago, and I think that the implementation was nice.
Let's say that you have to process incoming messages, each message has a type, so the common approach here it's to use a switch and do things like this:
We can avoid that switch code, and also we can avoid the need to modify that code each time that a new type is added, following this pattern:
This is nice right? So how we can achieve something like that?
First let's define an annotation and also an interface
and now let's use this annotation in a class:
The last part it's to write the MessageHandler class, in this class you just need to store in a map all your handlers instances, the key of the map will be the topic name and the value and instance of the class. Basically you getMessageHandler method will look something like:
The good thing with this solution it's that if you need to add a new handler for a new message, you just need to write the new class, add the annotation to the class and that's all. The rest of the code remains the same because it's generic and it's using reflection to load and create instances.
Hope this helps you a bit.
I just simply inject an IEnumerable
ConfigureServices in Startup.cs
Services Folder
MyController.cs
Extensions.cs
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).