Multiple Implementations of the Same Interface in asp.net Core or Named Services in .Net Core

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.

Next Post Previous Post
1 Comments
  • Anonymous
    Anonymous January 7, 2024 at 5:06 AM

    thx

Add Comment
comment url