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:
- Separation of Concerns: Different implementations can handle different concerns while adhering to the same contract.
- Flexibility: Switching between different implementations at runtime based on configuration or other criteria.
- 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.