Keyed Services in .net 8
Tags: dotnetcore, aspdotnet, api
Published: 2024-06-03 18:11:28


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:

```csharp
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).

```csharp
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:

```csharp
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:

```csharp
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:

```csharp
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:

```csharp
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