DEV Community

mohamed Tayel
mohamed Tayel

Posted on • Updated on

Optimal Dependency Injection with Keyed Services in ASP.NET Core 8

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>();
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
 }
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
moh_moh701 profile image
mohamed Tayel • Edited