The Dependency Injection (DI) system in .NET Core provides a powerful way to manage object dependencies and promote modular and testable code. While registering and resolving services is a well-known concept, there are scenarios where you might need to work with multiple implementations of the same interface based on a specific context. This is where named services come into play.
In this blog post, we’ll explore how to implement named services using a custom named service resolver.
The Scenario
Consider a scenario where you have different implementations of an IAnimal
interface, representing various types of animals. You want to be able to resolve the appropriate animal implementation based on its name, allowing you to interact with different animals through a common interface.
Defining the Interfaces and Implementations
First, let’s define the IAnimal
interface and its implementations Dog
and Cat
:
public interface IAnimal
{
void MakeSound();
}
public class Dog : IAnimal
{
public void MakeSound()
{
Console.WriteLine("Woof!");
}
}
public class Cat : IAnimal
{
public void MakeSound()
{
Console.WriteLine("Meow!");
}
}
Custom Named Service Resolver
To achieve named services, we’ll create a custom named service resolver that resolves a named service instance based on its type name. We’ll also create an interface INamedServiceResolver<T>
for the resolver:
public interface INamedServiceResolver<T>
{
T Resolve(string name);
}
public class NamedServiceResolver<T> : INamedServiceResolver<T>
{
private readonly IServiceProvider _serviceProvider;
public NamedServiceResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public T Resolve(string name)
{
return _serviceProvider.GetRequiredServiceByName<T>(name);
}
}
Extending the Service Provider
To enable named service resolution, we need to extend the service provider by adding a method to retrieve a named service based on its type name:
public static class ServiceProviderExtensions
{
public static T GetRequiredServiceByName<T>(this IServiceProvider serviceProvider, string name)
{
var namedServices = serviceProvider.GetServices<T>();
foreach (var service in namedServices)
{
var serviceName = service.GetType().Name;
if (string.Equals(serviceName, name, StringComparison.OrdinalIgnoreCase))
{
return service;
}
}
throw new InvalidOperationException($"No named service with the name '{name}' found.");
}
}
Putting It All Together
Let’s put everything together in the Main
method:
class Program
{
static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddScoped<IAnimal, Dog>()
.AddScoped<IAnimal, Cat>()
.AddScoped(typeof(INamedServiceResolver<>), typeof(NamedServiceResolver<>))
.BuildServiceProvider();
var namedServiceResolver = serviceProvider.GetRequiredService<INamedServiceResolver<IAnimal>>();
var dog = namedServiceResolver.Resolve("Dog");
dog.MakeSound(); // Output: Woof!
var cat = namedServiceResolver.Resolve("Cat");
cat.MakeSound(); // Output: Meow!
}
}
In this example, we register Dog
and Cat
implementations using the .AddScoped<IAnimal, Dog>()
and .AddScoped<IAnimal, Cat>()
methods. We also register the custom named service resolver using .AddScoped(typeof(INamedServiceResolver<>), typeof(NamedServiceResolver<>))
. Finally, we retrieve named services using the INamedServiceResolver<IAnimal>
instance.
Conclusion
Named services provide a flexible way to work with multiple implementations of the same interface based on a specific context. By implementing a custom named service resolver, you can easily resolve the appropriate service instance using its type name. This approach can be particularly useful when dealing with scenarios that require dynamic service selection based on context.
I hope this blog post has helped you understand how to implement named services in .NET Core Dependency Injection using a custom named service resolver. If you have any questions or suggestions, please feel free to leave a comment.
thx
ReplyDelete