Partager via


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 MessageWriter classe pour utiliser sa Write méthode. 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, vous devez modifier la Worker classe.
  • Si MessageWriter elle a des dépendances, la Worker classe doit également les configurer. 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 les problèmes suivants :

  • 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-le BuildServiceProvider pour créer le conteneur de services.
  • 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 MessageWriter concret, seulement l’interface IMessageWriter qu'elle implémente. Cela facilite la modification de l’implémentation utilisée par le service Worker sans modifier le service Worker.
  • Ne crée pas une instance de MessageWriter. Le conteneur d’adresses de disponibilité crée l’instance.

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 à résoudre est généralement appelé arborescence de dépendances, graphique de dépendances 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 IMessageWriter implémentations présentées dans les exemples précédents illustrent l’UTILISATION de base, et non 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 sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

            await Task.Delay(1_000, stoppingToken);
        }
    }
}

À l’aide du code précédent, il n’est pas nécessaire de mettre à jour Program.cs, car l’infrastructure fournit la journalisation.

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 est résolvable à partir du fournisseur de services, mais que les types FooService et BarService ne le sont pas. Le constructeur avec le ILogger<ExampleService> paramètre résout l’instance ExampleService . Même s’il existe un constructeur qui définit plus de paramètres, les types FooService et BarService ne sont pas résolvables par DI.

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 ExampleService code avec des paramètres de type ambigus pouvant être résolus par DI lève une exception. Ne faites pas cela : l'objectif est de montrer ce que signifient les types résolvables par DI ambigus.

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 DI tente de résoudre le type ExampleService, une exception est levée, car les deux constructeurs sont ambiguës.

Évitez l’ambiguïté en définissant un constructeur qui accepte les deux types résolvables DI à la place :

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 avoir créé un constructeur à partir de l'une de ces API, les IServiceCollection services fournis par le cadre dépendent de la façon dont vous avez configuré 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.

Notes

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.

Un service délimité doit toujours être utilisé à partir d’une étendue , soit d’une étendue implicite (par exemple, l’étendue par requête de ASP.NET Core) soit une étendue explicite créée avec IServiceScopeFactory.CreateScope().

Ne résolvez pas un service délimité directement à partir d’un singleton à l’aide d’une injection de constructeur ou en le demandant IServiceProvider dans le singleton. Cela entraîne le comportement du service étendu comme un singleton, ce qui peut entraîner un état incorrect lors du traitement des requêtes suivantes.

Il est acceptable de résoudre un service délimité dans un singleton si vous créez et utilisez une étendue explicite avec IServiceScopeFactory.

Il est également 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, pensez à la gestion de 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
objet
suppression
Plusieurs
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 Non 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. Considérons par exemple le code suivant :

services.AddSingleton<ExampleService>();

Ceci équivaut à inscrire le service avec le service et l’implémentation des mêmes types :

services.AddSingleton<ExampleService, ExampleService>();

Cette équivalence explique pourquoi plusieurs implémentations d’un service ne peuvent pas être inscrites en utilisant les méthodes qui n’acceptent pas un type de service explicite. Ces méthodes peuvent inscrire plusieurs instances d’un service, mais elles partagent toutes les mêmes types d'implémentation .

Toutes les méthodes d’inscription de service peuvent être utilisées 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>. IMessageWriter Il s’agit de la dernière implémentation à inscrire, tandis que la 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>();

Le TryAddSingleton n’a aucun effet, car il a déjà été ajouté et l'instruction « try » échoue. Le 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’un des mêmes types n’a pas déjà été ajouté. 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 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.

Lors de la résolution des services par IServiceProvider ou ActivatorUtilities, l'injection de constructeur nécessite un constructeur public.

Lorsqu'ActivatorUtilities résout les services, l'injection de constructeur 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 les services d’une étendue et que l’un de ces services prend un IServiceProvider, il s’agit d’une instance délimitée.

Pour définir la portée des services au sein d’implémentations de IHostedService, tels que le 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 d’autres services.
  • 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é

À compter de .NET 8, il existe une prise en charge des inscriptions de service et des recherches basées sur une clé, ce qui signifie qu’il est possible d’inscrire plusieurs services avec une clé différente et d’utiliser 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");

Ce key n’est pas limité à string. Il key peut s’agir de n’importe quoi object souhaité, tant que cela implémente Equals correctement.

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