Injection de dépendances .NET

.NET prend en charge le modèle de conception de logiciel avec injection de dépendances (DI), une technique permettant d’obtenir une inversion de contrôle (IoC) entre les classes et leurs dépendances. L’injection de dépendances dans .NET est une partie intégrée de l’infrastructure, ainsi que la configuration, la journalisation et le modèle d’options.

Une dépendance est un objet dont dépend un autre objet. Examinez la classe MessageWriter suivante avec une méthode Write dont dépendent d’autres classes :

public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

Une classe peut créer une instance de la classe MessageWriter pour utiliser sa méthode Write. Dans l’exemple suivant, la classe MessageWriter est une dépendance de la classe Worker :

public class Worker : BackgroundService
{
    private readonly MessageWriter _messageWriter = new();

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

La classe est créee et dépend directement de la classe MessageWriter. Les dépendances codées en dur, comme l’exemple précédent, posent problème et doivent être évitées pour les raisons suivantes :

  • Pour remplacer MessageWriter par une autre implémentation, la classe Worker doit être modifiée.
  • Si MessageWriter possède des dépendances, elles doivent être configurées par la classe Worker. Dans un grand projet comportant plusieurs classes dépendant de MessageWriter, le code de configuration est disséminé dans l’application.
  • Cette implémentation complique le test unitaire. L’application doit utiliser une classe MessageWriter fictive ou stub, ce qui est impossible avec cette approche.

L’injection de dépendances résout ces problèmes via :

  • L’utilisation d’une interface ou classe de base pour extraire l’implémentation des dépendances.
  • L’inscription de la dépendance dans un conteneur de service. .NET fournit un conteneur de service intégré, IServiceProvider. Les services sont généralement inscrits au démarrage de l’application et ajoutés à un IServiceCollection. Une fois tous les services ajoutés, utilisez BuildServiceProvider pour créer le conteneur de service.
  • Injection du service dans le constructeur de la classe où il est utilisé. Le framework prend la responsabilité de la création d’une instance de la dépendance et de sa suppression lorsqu’elle n’est plus nécessaire.

Par exemple, l’interface IMessageWriter définit la méthode Write :

namespace DependencyInjection.Example;

public interface IMessageWriter
{
    void Write(string message);
}

Cette interface est implémentée par un type concret, MessageWriter :

namespace DependencyInjection.Example;

public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

L’exemple de code inscrit le service IMessageWriter avec le type MessageWriterconcret. La méthode AddSingleton inscrit le service avec une durée de vie singleton, la durée de vie de l’application. Les durées de vie du service sont décrites plus loin dans cet article.

using DependencyInjection.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();

using IHost host = builder.Build();

host.Run();

Dans le code précédent, l’exemple de code :

  • Crée une instance du générateur d’applications hôtes.

  • Configure les services en inscrivant :

    • Worker en tant que service hébergé. Pour plus d’informations, consultez Services Worker dans .NET.
    • L’interface IMessageWriter en tant que service singleton avec une implémentation correspondante de la classe MessageWriter.
  • Génère l’hôte et l’exécute.

L’hôte contient le fournisseur de services d’injection de dépendances. Il contient également tous les autres services pertinents nécessaires pour instancier automatiquement le Worker et fournir l’implémentation correspondante IMessageWriter en tant qu’argument.

namespace DependencyInjection.Example;

public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

À l’aide du modèle d’injection de dépendance, le service worker :

  • N’utilise pas le type MessageWriterconcret, uniquement l’interface IMessageWriter qui l’implémente. Cela facilite la modification de l’implémentation que le service worker utilise sans modifier le service worker.
  • Ne crée pas une instance de MessageWriter. L’instance est créée par le conteneur d’injection de dépendances.

L’implémentation de l’interface IMessageWriter peut être améliorée à l’aide de l’API de journalisation intégrée :

namespace DependencyInjection.Example;

public class LoggingMessageWriter(
    ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
    public void Write(string message) =>
        logger.LogInformation("Info: {Msg}", message);
}

La méthode AddSingleton mise à jour inscrit la nouvelle implémentation IMessageWriter :

builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

Le type HostApplicationBuilder (builder) fait partie du package NuGet Microsoft.Extensions.Hosting.

LoggingMessageWriter dépend de ILogger<TCategoryName>, qu’il demande dans le constructeur. ILogger<TCategoryName> est un service fourni par l’infrastructure.

Il n’est pas rare que l’injection de dépendances soit utilisée de manière chaînée. Dans ce cas, chaque dépendance demandée demande à son tour ses propres dépendances. Le conteneur résout les dépendances dans le graphique et retourne le service entièrement résolu. L’ensemble collectif de dépendances qui doivent être résolues est généralement appelé arborescence des dépendances, graphique de dépendance ou graphique d’objet.

Le conteneur résout ILogger<TCategoryName> en tirant parti de types ouverts (génériques), ce qui élimine la nécessité d’inscrire chaque type construit (générique).

Avec la terminologie d’injection de dépendances, un service :

  • Est généralement un objet qui fournit un service à d’autres objets, tels que le service IMessageWriter.
  • N’est pas lié à un service web, bien que le service puisse utiliser un service web.

L’infrastructure fournit un système de journalisation robuste. Les implémentations IMessageWriter indiquées dans les exemples précédents ont été écrites pour démontrer l’injection de dépendances de base, et non pour implémenter la journalisation. La plupart des applications ne doivent pas avoir besoin d’écrire des enregistreurs d’événements. Le code suivant illustre l’utilisation de la journalisation par défaut, qui exige uniquement que le Worker soit inscrit en tant que service hébergé AddHostedService :

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger) =>
        _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

En utilisant le code précédent, il n’est pas nécessaire de mettre à jour Program.cs, car la journalisation est fournie par l’infrastructure.

Règles de découverte de plusieurs constructeurs

Lorsqu’un type définit plusieurs constructeurs, le fournisseur de services dispose d’une logique pour déterminer le constructeur à utiliser. Le constructeur avec le plus grand nombre de paramètres pour lesquels les types pouvant être résolus par injection de dépendances est sélectionné. Considérez l’exemple de service C# suivant :

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(FooService fooService, BarService barService)
    {
        // omitted for brevity
    }
}

Dans le code précédent, supposons que la journalisation a été ajoutée et qu’elle peut être résolue à partir du fournisseur de services, mais les types FooService et BarService ne le sont pas. Le constructeur avec le paramètre ILogger<ExampleService> est utilisé pour résoudre l’instance ExampleService. Même s’il existe un constructeur qui définit d’autres paramètres, les types FooService et BarService ne peuvent pas être résolus par injection de dépendances.

En cas d’ambiguïté lors de la découverte des constructeurs, une exception est levée. Considérez l’exemple de service C# suivant :

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Avertissement

Le code ExampleService avec des paramètres de type ambigus pouvant être résolus par injection de dépendances lève une exception. Ne le faites pas, il est destiné à montrer ce que l’on entend par « types ambigus pouvant être résolus par injections de dépendances».

Dans l’exemple précédent, il existe trois constructeurs. Le premier constructeur est sans paramètre et ne nécessite aucun service du fournisseur de services. Supposons que la journalisation et les options ont été ajoutées au conteneur d’injections de dépendances et qu’il s’agit de services pouvant être résolus par injection de service. Lorsque le conteneur d’injection de dépendances tente de résoudre le type ExampleService, il lève une exception, car les deux constructeurs sont ambigus.

Vous pouvez éviter toute ambiguïté en définissant à la place un constructeur qui accepte les deux types pouvant être résolus par injection de dépendances :

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(
        ILogger<ExampleService> logger,
        IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Inscrire des groupes de services avec des méthodes d’extension

Les extensions Microsoft utilisent une convention pour l’inscription d’un groupe de services associés. La convention consiste à utiliser une seule méthode d’extension Add{GROUP_NAME} pour inscrire tous les services requis par une fonctionnalité d’infrastructure. Par exemple, la méthode d’extension AddOptions inscrit tous les services requis pour l’utilisation des options.

Services fournis par le framework

Lorsque vous utilisez l’un des modèles de générateur d’hôtes ou d’applications disponibles, les valeurs par défaut sont appliquées, et les services inscrits par l’infrastructure. Prenons des modèles de générateur d’hôtes et d’applications parmi les plus populaires :

Après la création d’un générateur à partir de l’une de ces API, IServiceCollection comprend des services définis par l’infrastructure, en fonction de la configuration de l’hôte. Dans le cas des applications basées sur les modèles .NET, l’infrastructure inscrit des centaines de services.

Le tableau suivant répertorie un petit exemple de ces services inscrits dans l’infrastructure :

Type de service Durée de vie
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Singleton
IHostApplicationLifetime Singleton
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Temporaire
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticListener Singleton
System.Diagnostics.DiagnosticSource Singleton

Durées de service

Les services peuvent être inscrits avec l’une des durées de vie suivantes :

Les sections suivantes décrivent chacune des durées de vie précédentes. Choisissez une durée de vie appropriée pour chaque service inscrit.

Temporaire

Des services à durée de vie temporaire (Transient) sont créés chaque fois qu’ils sont demandés à partir du conteneur de service. Pour inscrire un service comme étant temporaire, appelez AddTransient.

Dans les applications qui traitent les demandes, les services temporaires sont supprimés à la fin de la demande. Cette durée de vie entraîne des allocations par requête, car les services sont à chaque fois résolus et construits. Pour obtenir plus d’informations, consultez Recommandations relatives à l’injection de dépendances : Conseils sur l’interface IDisposable pour les instances temporaires et partagées.

Délimité

Pour les applications web, une durée de vie étendue indique que les services sont créés une fois par demande client (connexion). Inscrivez les services délimités avec AddScoped.

Dans les applications qui traitent les demandes, les services délimités sont supprimés à la fin de la requête.

Lors de l’utilisation d’Entity Framework Core, la méthode d’extension AddDbContext inscrit les types DbContext avec une durée de vie délimitée par défaut.

Notes

Ne résolvez pas un service délimité à partir d’un singleton et veillez à ne pas le faire indirectement, par exemple, via un service temporaire. L’état du service risque de ne pas être correct lors du traitement des requêtes suivantes. Il est bon de :

  • Résoudre un service singleton à partir d’un service délimité ou temporaire.
  • Résoudre un service délimité à partir d’un autre service délimité ou temporaire.

Par défaut, dans l’environnement de développement, la résolution d’un service à partir d’un autre service avec une durée de vie plus longue lève une exception. Pour plus d’informations, consultez Validation de l’étendue.

Singleton

Les services de durée de vie Singleton sont créés soit :

  • La première fois qu’ils sont demandés.
  • Par le développeur, lors de la fourniture d’une instance d’implémentation directement au conteneur. Cette approche est rarement nécessaire.

Chaque requête suivante de l’implémentation de service à partir du conteneur d’injection de dépendances utilise la même instance. Si l’application exige un comportement singleton, autorisez le conteneur de service à gérer la durée de vie du service. N’implémentez pas le modèle de conception singleton et fournissez du code pour supprimer le singleton. Les services ne doivent jamais être supprimés par le code qui a résolu le service à partir du conteneur. Si un type ou une fabrique est inscrit en tant que singleton, le conteneur supprime automatiquement le singleton.

Inscrivez les services singleton avec AddSingleton. Les services Singleton doivent être thread-safe et sont souvent utilisés dans les services sans état.

Dans les applications qui traitent les requêtes, les services singleton sont supprimés lorsque ServiceProvider est supprimé lors de l’arrêt de l’application. Étant donné que la mémoire n’est pas libérée tant que l’application n’est pas arrêtée, envisagez d’utiliser la mémoire avec un service singleton.

Méthodes d’inscription du service

L’infrastructure fournit des méthodes d’extension d’inscription du service qui sont utiles dans des scénarios spécifiques :

Méthode Automatique
object
suppression
Multiple
implémentations
Passage d’args
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()

Exemple :

services.AddSingleton<IMyDep, MyDep>();
Oui Oui Non
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

Exemples :

services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep(99));
Oui Oui Oui
Add{LIFETIME}<{IMPLEMENTATION}>()

Exemple :

services.AddSingleton<MyDep>();
Oui No Non
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})

Exemples :

services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep(99));
Non Oui Oui
AddSingleton(new {IMPLEMENTATION})

Exemples :

services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));
Non Non Oui

Pour plus d’informations sur la suppression de type, consultez la section Suppression des services.

L’inscription d’un service avec uniquement un type d’implémentation revient à inscrire ce service avec le même type d’implémentation et de service. C’est pourquoi plusieurs implémentations d’un service ne peuvent pas être inscrites à l’aide des méthodes qui n’acceptent pas de type de service explicite. Ces méthodes peuvent inscrire plusieurs instances d’un service, mais elles auront toutes le même type d’implémentation.

L’une des méthodes d’inscription de service ci-dessus peut être utilisée pour inscrire plusieurs instances de service du même type de service. Dans l’exemple suivant, AddSingleton est appelé deux fois avec IMessageWriter comme type de service. Le deuxième appel à AddSingleton remplace le précédent lorsqu’il est résolu en tant que IMessageWriter et s’ajoute au précédent lorsque plusieurs services sont résolus via IEnumerable<IMessageWriter>. Les services apparaissent dans l’ordre dans lequel ils ont été inscrits lorsqu’ils sont résolus via IEnumerable<{SERVICE}>.

using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();

using IHost host = builder.Build();

_ = host.Services.GetService<ExampleService>();

await host.RunAsync();

L’exemple de code source précédent inscrit deux implémentations de IMessageWriter.

using System.Diagnostics;

namespace ConsoleDI.IEnumerableExample;

public sealed class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is LoggingMessageWriter);

        var dependencyArray = messageWriters.ToArray();
        Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
        Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
    }
}

ExampleService définit deux paramètres de constructeur : un seul IMessageWriteret un IEnumerable<IMessageWriter>. Le seul IMessageWriter est la dernière implémentation à avoir été inscrite, tandis que IEnumerable<IMessageWriter> représente toutes les implémentations inscrites.

L’infrastructure fournit également des méthodes d’extension TryAdd{LIFETIME}, qui inscrivent le service uniquement s’il n’y a pas encore d’implémentation inscrite.

Dans l’exemple suivant, l’appel à AddSingleton inscrit ConsoleMessageWriter en tant qu’implémentation pour IMessageWriter. L’appel à TryAddSingleton n’a aucun effet car IMessageWriter a déjà une implémentation inscrite :

services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();

TryAddSingleton n’a aucun effet, car il a déjà été ajouté et le « try » échoue. ExampleService affirme ce qui suit :

public class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is ConsoleMessageWriter);
        Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
    }
}

Pour plus d'informations, consultez les pages suivantes :

Les méthodes TryAddEnumerable(ServiceDescriptor) inscrivent le service uniquement en l’absence d’une implémentation du même type. Plusieurs services sont résolus par le biais de IEnumerable<{SERVICE}>. Lors de l’inscription des services, ajoutez une instance si l’une des mêmes types n’a pas déjà été ajoutée. Les auteurs de bibliothèque utilisent TryAddEnumerable pour éviter d’inscrire plusieurs copies d’une implémentation dans le conteneur.

Dans l’exemple suivant, le premier appel à TryAddEnumerable inscrit MessageWriter en tant qu’implémentation pour IMessageWriter1. La deuxième appel inscrit MessageWriter pour IMessageWriter2. Le troisième appel n’a aucun effet car IMessageWriter1 a déjà une implémentation inscrite de MessageWriter :

public interface IMessageWriter1 { }
public interface IMessageWriter2 { }

public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}

services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());

L’inscription de service est généralement indépendante de l’ordre, sauf lors de l’inscription de plusieurs implémentations du même type.

IServiceCollection est une collection d’objets ServiceDescriptor. L’exemple suivant montre comment inscrire un service en créant et en ajoutant un ServiceDescriptor :

string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
    typeof(IMessageWriter),
    _ => new DefaultMessageWriter(secretKey),
    ServiceLifetime.Transient);

services.Add(descriptor);

Les méthodes intégrées Add{LIFETIME} utilisent la même approche. Par exemple, consultez le code source AddScoped.

Comportement d’injection de constructeurs

Les services peuvent être résolus à l’aide de :

Les constructeurs peuvent accepter des arguments qui ne sont pas fournis par l’injection de dépendances, mais les arguments doivent affecter des valeurs par défaut.

Lorsque des services sont résolus par IServiceProvider ou ActivatorUtilities, l’injection de constructeurs exige un constructeur public.

Lorsque des services sont résolus par ActivatorUtilities, l’injection de constructeurs exige qu’un seul constructeur applicable existe. Les surcharges de constructeurs sont prises en charge, mais une seule peut exister dont les arguments peuvent tous être satisfaits par l’injection de dépendances.

Validation de l’étendue

Lorsque l’application s’exécute dans l’environnement Development et appelle CreateApplicationBuilder pour générer l’hôte, le fournisseur de services par défaut effectue différentes vérifications :

  • Les services délimités ne sont pas résolus à partir du fournisseur de services racine.
  • Les services délimités ne sont pas injectés dans les singletons.

Le fournisseur de services racine est créé quand BuildServiceProvider est appelé. La durée de vie du fournisseur de services racine correspond à la durée de vie de l’application quand le fournisseur démarre avec l’application et qu’il est supprimé quand l’application s’arrête.

Les services Scoped sont supprimés par le conteneur qui les a créés. Si un service délimité est créé dans le conteneur racine, la durée de vie du service est promue en singleton, car elle est supprimée par le conteneur racine seulement quand l’application est arrêtée. La validation des étendues du service permet de traiter ces situations quand BuildServiceProvider est appelé.

Scénarios d’étendue

IServiceScopeFactory est toujours inscrit en tant que singleton, mais IServiceProvider peut varier en fonction de la durée de vie de la classe contenante. Par exemple, si vous résolvez des services à partir d’une étendue et que l’un de ces services prend un IServiceProvider, il s’agit d’une instance délimitée.

Pour obtenir des services d’étendue dans des implémentations de IHostedService, telles que BackgroundService n’injectez pas les dépendances de service via l’injection de constructeur. Au lieu de cela, injectez IServiceScopeFactory, créez une étendue, puis résolvez les dépendances de l’étendue pour utiliser la durée de vie de service appropriée.

namespace WorkerScope.Example;

public sealed class Worker(
    ILogger<Worker> logger,
    IServiceScopeFactory serviceScopeFactory)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (IServiceScope scope = serviceScopeFactory.CreateScope())
            {
                try
                {
                    logger.LogInformation(
                        "Starting scoped work, provider hash: {hash}.",
                        scope.ServiceProvider.GetHashCode());

                    var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
                    var next = await store.GetNextAsync();
                    logger.LogInformation("{next}", next);

                    var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
                    await processor.ProcessAsync(next);
                    logger.LogInformation("Processing {name}.", next.Name);

                    var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
                    await relay.RelayAsync(next);
                    logger.LogInformation("Processed results have been relayed.");

                    var marked = await store.MarkAsync(next);
                    logger.LogInformation("Marked as processed: {next}", marked);
                }
                finally
                {
                    logger.LogInformation(
                        "Finished scoped work, provider hash: {hash}.{nl}",
                        scope.ServiceProvider.GetHashCode(), Environment.NewLine);
                }
            }
        }
    }
}

Dans le code précédent, pendant l’exécution de l’application, le service en arrière-plan :

  • Dépend de IServiceScopeFactory.
  • Crée un IServiceScope pour résoudre des services supplémentaires.
  • Résout les services délimités pour la consommation.
  • Fonctionne sur le traitement des objets, puis leurs relais, puis les marque comme traités.

À partir de l’exemple de code source, vous pouvez voir comment les implémentations de IHostedService peuvent tirer parti des durées de vie de service étendues.

Services à clé

.NET 8 et versions ultérieures prennent en charge les inscriptions et recherches de service basées sur une clé. Cela rend possible l’inscription de plusieurs services avec une autre clé et l’utilisation de cette clé pour la recherche.

Prenez par exemple le cas où vous avez différentes implémentations de l’interface IMessageWriter : MemoryMessageWriter et QueueMessageWriter.

Vous pouvez inscrire ces services à l’aide de la surcharge des méthodes d’inscription de service (évoquées précédemment) qui prennent en charge une clé en tant que paramètre :

services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");

Cette key n’est pas limitée à string. Il peut s’agir de n’importe quel object souhaité, tant que le type implémente correctement les Equals.

Dans le constructeur de la classe qui utilise IMessageWriter, vous ajoutez le FromKeyedServicesAttribute pour spécifier la clé du service à résoudre :

public class ExampleService
{
    public ExampleService(
        [FromKeyedServices("queue")] IMessageWriter writer)
    {
        // Omitted for brevity...
    }
}

Voir aussi