In complex applications, it's common to have multiple implementations of the same interface. However, this can lead to ambiguity and bugs when resolving these dependencies using the built-in dependency injection (DI) container in ASP.NET Core. ASP.NET Core 8 introduces the concept of keyed services to solve this problem, allowing you to register services with a unique key. This article will explain how to use this new feature effectively.
The Problem with Multiple Implementations
When you have multiple implementations of the same interface and register them without any distinguishing mechanism, the DI container will resolve the last registered service by default. This can cause unexpected behavior.
Example:
builder.Services.AddSingleton<INotificationService, MailNotificationService>();
builder.Services.AddSingleton<INotificationService, PhoneNotificationService>();
builder.Services.AddSingleton<INotificationService, PushNotificationService>();
In the example above, when INotificationService
is resolved, it will always return the PushNotificationService
because it was the last one registered.
Introducing Keyed Services
ASP.NET Core 8 introduces keyed services, allowing you to register services with a key. This key is typically a string but can be any type. This feature lets you specify which implementation to use when resolving the dependency.
Registering Keyed Services:
To register services with a key, use the AddKeyedSingleton
method (or AddKeyedScoped
/ AddKeyedTransient
based on the desired lifetime).
builder.Services.AddKeyedSingleton<INotificationService, MailNotificationService>("mail");
builder.Services.AddKeyedSingleton<INotificationService, PhoneNotificationService>("phone");
builder.Services.AddKeyedSingleton<INotificationService, PushNotificationService>("push");
Resolving Keyed Services
To resolve a keyed service, you use the IKeyedServiceProvider
interface. This interface provides methods to get the service instance based on the key.
Example:
public class ShoppingCartService : IShoppingCartService
{
private readonly IKeyedServiceProvider _serviceProvider;
public ShoppingCartService(IKeyedServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void ClearCart(string notificationType)
{
var notificationService = _serviceProvider.GetRequiredKeyedService<INotificationService>(notificationType);
notificationService.SendNotification("Cart cleared");
}
}
In this example, the ShoppingCartService
class can use different notification services based on the provided key ("mail"
, "phone"
, or "push"
). This is a powerful way to handle multiple implementations and provides flexibility in selecting the appropriate service at runtime.
Benefits of Keyed Services
- Clarity and Control: By explicitly specifying which implementation to use, you avoid ambiguity and make the code more understandable.
- Flexibility: Keyed services allow you to switch between different implementations based on context or configuration easily.
- Maintainability: Adding or removing implementations becomes simpler and less risky, as it does not affect other parts of the application.
Full Example
Below is a full example demonstrating the registration and resolution of keyed services in ASP.NET Core 8:
Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Registering keyed services
builder.Services.AddKeyedSingleton<INotificationService, MailNotificationService>("mail");
builder.Services.AddKeyedSingleton<INotificationService, PhoneNotificationService>("phone");
builder.Services.AddKeyedSingleton<INotificationService, PushNotificationService>("push");
var app = builder.Build();
app.Run();
Notification Services:
public interface INotificationService
{
void SendNotification(string message);
}
public class MailNotificationService : INotificationService
{
public void SendNotification(string message)
{
// Logic for sending mail notification
}
}
public class PhoneNotificationService : INotificationService
{
public void SendNotification(string message)
{
// Logic for sending phone notification
}
}
public class PushNotificationService : INotificationService
{
public void SendNotification(string message)
{
// Logic for sending push notification
}
}
ShoppingCartService:
public interface IShoppingCartService
{
void ClearCart(string notificationType);
}
public class ShoppingCartService : IShoppingCartService
{
private readonly INotificationService _notificationService;
public ShoppingCartService([FromKeyedServices("mail")] INotificationService notificationService)
{
_notificationService = notificationService;
}
public void ClearCart()
{
_notificationService.SendNotification("Cart cleared");
}
}
Conclusion
Keyed services in ASP.NET Core 8 provide a robust way to manage multiple implementations of the same interface, offering clarity, control, and flexibility. By using this feature, you can avoid the common pitfalls of dependency resolution in complex applications and ensure your services are resolved accurately and efficiently.
Source
For more detailed information and to stay updated with the latest features in .NET 8, you can refer to the What's New in .NET 8 course on Pluralsight.
https://app.pluralsight.com/library/courses/dot-net-8-whats-new/table-of-contents
Top comments (1)
code github.com/mohamedtayel1980/DotNet...