Les Keyed Services
Introduction
Apparus en .NET 8, les Keyed Services représentent une évolution bienvenue du conteneur d'injection de dépendances de .NET. Ils sont une alternative aux usines (factories) et aux recherches dans des services obtenus depuis le conteneur d'injection de dépendances, tout en rendant l'intention du code plus lisible.
Implémentation
Voici une interface et les classes suivantes dont le but est d'envoyer des notifications par mail ou SMS :
public interface INotificationSender
{
Task SendAsync(string recipient, string message);
}
public class EmailSender : INotificationSender
{
public Task SendAsync(string recipient, string message)
{
Console.WriteLine("Message envoyé par Email");
return Task.CompletedTask;
}
}
public class SmsSender : INotificationSender
{
public Task SendAsync(string recipient, string message)
{
Console.WriteLine("Message envoyé par Sms");
return Task.CompletedTask;
}
}
Avant la sortie de .NET 8, nous enregistrions ces services dans le conteneur d'injection de dépendances de la manière suivante :
builder.Services.AddScoped<INotificationSender, EmailSender>();
builder.Services.AddScoped<INotificationSender, SmsSender>();
Et nous injections ces services dans une classe de la manière suivante :
public class NotificationService
{
public NotificationService(IEnumerable<INotificationSender> senders)
{
...
}
}
Puis, en fonction d'un critère de sélection, nous recherchions le service dans senders.
Qu'apportent les Keyed Services dans cette situation ?
Avec la méthode AddKeyedScoped, chaque service est enregistré dans le conteneur d'injection de dépendances avec une clé :
builder.Services.AddKeyedScoped<INotificationSender, EmailSender>("email");
builder.Services.AddKeyedScoped<INotificationSender, SmsSender>("sms");
Ainsi, l'injection de dépendance du service recherché est facilité en utilisant la métadonnée FromKeyedServices et la clé du service :
public class NotificationService
{
private readonly INotificationSender _sender;
public NotificationService([FromKeyedServices("email")] INotificationSender sender)
{
_sender = sender;
}
}
Le conteneur injecte directement une instance de la classe EmailSender.
Les méthodes disponibles
Les Keyed Services présentent les mêmes durées de vie que celles que nous connaissons déjà :
Par exemple :
builder.Services.AddKeyedSingleton<ICache, RedisCache>("redis");
builder.Services.AddKeyedScoped<IPaymentService, StripePaymentService>("stripe");
builder.Services.AddKeyedTransient<IFormatter, JsonFormatter>("json");
Résolution manuelle
Il est possible d'obtenir un service à partir de sa clé via IServiceProvider. Cette implémentation lève une exception si le service n'a pu être trouvé :
Cette implémentation retourne null si le service n'a pu être trouvé :
Les clés ne sont pas limitées aux chaînes
Une clé est de type object. On peut donc utiliser une valeur de type numérique :
Ou un élément d'une énumération :
L'utilisation d'une énumération est souvent préférable aux chaînes de caractères ou entiers, car elle apporte davantage de fiabilité entre l'enregistrement et l'obtention du service depuis le conteneur d'injection de dépendances. Par exemple :
Puis :
builder.Services.AddKeyedScoped<INotificationSender, EmailSender>(
NotificationChannel.Email);
builder.Services.AddKeyedScoped<INotificationSender, SmsSender>(
NotificationChannel.Sms);
Et enfin :
public NotificationService(
[FromKeyedServices(NotificationChannel.Email)]
INotificationSender sender)
{
}
Faut-il remplacer le pattern Strategy ?
Les Keyed Services apparaissent à première vue comme un alternative au design pattern Strategy. Si le choix de l'implémentation est au démarrage ou lors de l'ajout du service dans le conteneur d'injection de dépendances, je trouve que les Keyed Services sont une implémentation appropriée.
En revanche, si le choix dépend de données métier connues uniquement lors de l'exécution (par exemple, le type de client, une préférence utilisateur, etc.), les design patterns Strategy ou Factory Method sont plus adaptés. La logique de sélection appartient alors davantage au domaine métier qu'au conteneur d'injection de dépendances.