Register Multiple Services with a Single Interface in .NET Core

Introduction

In modern software development, the use of interfaces is crucial for achieving clean architecture and adhering to the principles of dependency injection. Often, you may find yourself in a scenario where multiple implementations of a single interface are needed. In .NET Core, registering multiple services with a single interface can be accomplished seamlessly using the built-in Dependency Injection (DI) container.

Why Register Multiple Services with a Single Interface?

Before diving into the implementation, let’s understand why you might need to register multiple services under a single interface:

  1. Separation of Concerns: Different implementations can handle different concerns while adhering to the same contract.
  2. Flexibility: Switching between different implementations at runtime based on configuration or other criteria.
  3. Testing: Easily mock or stub different implementations for unit testing.

Example Scenario

Let's consider a scenario where you have an interface INotificationService with multiple implementations such as EmailNotificationService, SMSNotificationService, and PushNotificationService.

1. Define the Interface

First, define the INotificationService interface:

public interface INotificationService
{
    void SendNotification(string message);
}

2. Implement the Interface

Next, create multiple implementations of the INotificationService interface:

public class EmailNotificationService : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Email: {message}");
    }
}

public class SMSNotificationService : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"SMS: {message}");
    }
}

public class PushNotificationService : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Push: {message}");
    }
}

3. Register the Services in the DI Container

In the Startup.cs or Program.cs (for .NET 6 and later) file, register the services with the DI container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<INotificationService, EmailNotificationService>();
    services.AddTransient<INotificationService, SMSNotificationService>();
    services.AddTransient<INotificationService, PushNotificationService>();
}

4. Resolve the Services

To resolve all implementations of INotificationService, you can inject an IEnumerable<INotificationService>:

public class NotificationHandler
{
    private readonly IEnumerable<INotificationService> _notificationServices;

    public NotificationHandler(IEnumerable<INotificationService> notificationServices)
    {
        _notificationServices = notificationServices;
    }

    public void NotifyAll(string message)
    {
        foreach (var service in _notificationServices)
        {
            service.SendNotification(message);
        }
    }
}

5. Use the NotificationHandler

Inject the NotificationHandler wherever needed:

public class SomeController : ControllerBase
{
    private readonly NotificationHandler _notificationHandler;

    public SomeController(NotificationHandler notificationHandler)
    {
        _notificationHandler = notificationHandler;
    }

    public IActionResult SendNotifications(string message)
    {
        _notificationHandler.NotifyAll(message);
        return Ok();
    }
}

Additional Tips

  • Configuration-Based Resolution: Use configuration files or settings to determine which implementation to use at runtime.
  • Named Services: Use named services if you need to resolve specific implementations based on a name or key.
  • Factory Pattern: Implement the factory pattern to create instances of different implementations dynamically.

Conclusion

Registering multiple services with a single interface in .NET Core is straightforward and enhances the flexibility and testability of your application. By leveraging the built-in DI container, you can easily manage different implementations and switch between them as needed. This approach promotes a clean, maintainable, and scalable architecture in your .NET Core applications.


Similar Articles