Partager via


Tutoriel : Utiliser l’injection de dépendances dans .NET

Ce tutoriel montre comment utiliser l’injection de dépendances (DI) dans .NET. Avec les extensions Microsoftl’injection de dépendances est gérée en ajoutant des services et en les configurant dans IServiceCollection. L’interface IHost expose l’instance IServiceProvider, qui agit en tant que conteneur de tous les services inscrits.

Dans ce tutoriel, vous allez apprendre à :

  • Créer une application console .NET qui utilise l’injection de dépendances
  • Générer et configurer un hôte générique
  • Écrire plusieurs interfaces et implémentations correspondantes
  • Utiliser la durée de vie et l’étendue du service pour l’injection de dépendances

Conditions préalables

  • .NET Core 3.1 SDK ou version ultérieure.
  • Connaissance de la création d’applications .NET et de l’installation de packages NuGet.

Créer une application console

À l’aide de la commande dotnet new ou d’un Assistant Nouveau projet IDE, créez une application console .NET nommée ConsoleDI.Example. Ajoutez le package NuGet microsoft.Extensions.Hosting au projet.

Votre nouveau fichier projet d’application console doit ressembler à ce qui suit :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ConsoleDI.Example</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
  </ItemGroup>

</Project>

Important

Dans cet exemple, l'Microsoft.Extensions.Hosting package NuGet est nécessaire pour générer et exécuter l’application. Certains métapackages peuvent contenir le package Microsoft.Extensions.Hosting, auquel cas une référence de package explicite n’est pas nécessaire.

Ajouter des interfaces

Dans cet exemple d’application, vous allez découvrir comment l’injection de dépendances gère le cycle de vie des services. Vous allez créer plusieurs interfaces qui représentent différentes durées de vie de service. Ajoutez les interfaces suivantes au répertoire racine du projet :

IReportServiceLifetime.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IReportServiceLifetime
{
    Guid Id { get; }

    ServiceLifetime Lifetime { get; }
}

L’interface IReportServiceLifetime définit :

  • Propriété Guid Id qui représente l’identificateur unique du service.
  • Propriété ServiceLifetime qui représente la durée de vie du service.

IExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleTransientService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}

IExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleScopedService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}

IExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleSingletonService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}

Toutes les sous-interfaces de IReportServiceLifetime implémentent explicitement IReportServiceLifetime.Lifetime avec une implémentation par défaut. Par exemple, IExampleTransientService implémente explicitement IReportServiceLifetime.Lifetime avec la valeur ServiceLifetime.Transient.

Ajouter des implémentations par défaut

Les exemples d’implémentations initialisent toutes leur propriété Id avec le résultat de Guid.NewGuid(). Ajoutez les classes d’implémentation par défaut suivantes pour les différents services au répertoire racine du projet :

ExampleTransientService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleTransientService : IExampleTransientService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleScopedService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleScopedService : IExampleScopedService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleSingletonService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleSingletonService : IExampleSingletonService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

Chaque implémentation est définie comme internal sealed et implémente son interface correspondante. Ils ne doivent pas être internal ou sealed, mais il est courant de traiter les implémentations comme des internal pour éviter la fuite de types d’implémentation vers des consommateurs externes. En outre, comme chaque type ne sera pas étendu, il est marqué comme sealed. Par exemple, ExampleSingletonService implémente IExampleSingletonService.

Ajouter un service qui nécessite une injection de dépendances

Ajoutez la classe de rapporteur de durée de vie de service suivante, qui agit en tant que service, à l’application console :

ServiceLifetimeReporter.cs

namespace ConsoleDI.Example;

internal sealed class ServiceLifetimeReporter(
    IExampleTransientService transientService,
    IExampleScopedService scopedService,
    IExampleSingletonService singletonService)
{
    public void ReportServiceLifetimeDetails(string lifetimeDetails)
    {
        Console.WriteLine(lifetimeDetails);

        LogService(transientService, "Always different");
        LogService(scopedService, "Changes only with lifetime");
        LogService(singletonService, "Always the same");
    }

    private static void LogService<T>(T service, string message)
        where T : IReportServiceLifetime =>
        Console.WriteLine(
            $"    {typeof(T).Name}: {service.Id} ({message})");
}

Le ServiceLifetimeReporter définit un constructeur qui requiert chacune des interfaces de service mentionnées ci-dessus, c’est-à-dire IExampleTransientService, IExampleScopedServiceet IExampleSingletonService. L’objet expose une méthode unique qui permet au consommateur de créer un rapport sur le service avec un paramètre lifetimeDetails donné. Lorsqu’elle est appelée, la méthode ReportServiceLifetimeDetails journalise l’identificateur unique de chaque service avec le message de durée de vie du service. Les messages de journal permettent de visualiser la durée de vie du service.

Inscrire des services pour l’injection de dépendances

Mettez à jour Program.cs avec le code suivant :

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

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();

using IHost host = builder.Build();

ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");

await host.RunAsync();

static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
    using IServiceScope serviceScope = hostProvider.CreateScope();
    IServiceProvider provider = serviceScope.ServiceProvider;
    ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine("...");

    logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine();
}

Chaque méthode d’extension services.Add{LIFETIME}<{SERVICE}> ajoute (et éventuellement configure) des services. Nous vous recommandons de suivre cette convention pour les applications. Ne placez pas les méthodes d’extension dans l’espace de noms Microsoft.Extensions.DependencyInjection, sauf si vous créez un package Microsoft officiel. Méthodes d’extension définies dans l’espace de noms Microsoft.Extensions.DependencyInjection :

  • sont affichées dans IntelliSense sans nécessiter de directives using supplémentaires.
  • Réduisez le nombre de directives de using requises dans les classes Program ou Startup où ces méthodes d’extension sont généralement appelées.

L’application :

Conclusion

Dans cet exemple d’application, vous avez créé plusieurs interfaces et implémentations correspondantes. Chacun de ces services est identifié de manière unique et associé à un ServiceLifetime. L'application d'exemple démontre comment enregistrer des implémentations de service pour une interface, et comment enregistrer des classes pures sans interfaces sous-jacentes. L’exemple d’application montre ensuite comment les dépendances définies en tant que paramètres de constructeur sont résolues au moment de l’exécution.

Lorsque vous exécutez l’application, elle affiche une sortie similaire à ce qui suit :

// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// 
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)

À partir de la sortie de l’application, vous pouvez voir que :

  • Les services Transient sont toujours distincts, une nouvelle instance est créée à chaque extraction du service.
  • Les services Scoped ne changent qu'avec une nouvelle étendue, mais sont la même instance à l'intérieur d'une étendue.
  • Singleton services sont toujours identiques, une nouvelle instance n’est créée qu’une seule fois.

Voir aussi