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 utilisant 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

Prérequis

  • SDK .NET Core 3.1 ou version ultérieure
  • Bonne connaissance en matière de 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 se présenter comme 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.Hosting" Version="8.0.0" />
  </ItemGroup>

</Project>

Important

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

Ajouter des interfaces

Dans cet exemple d’application, vous allez découvrir la façon dont l’injection de dépendances gère la durée de vie de service. Vous allez créer plusieurs interfaces représentant 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 l’élément IReportServiceLifetime.Lifetime avec une valeur 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 des 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. 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, à savoir, IExampleTransientService, IExampleScopedService et IExampleSingletonService. L’objet expose une méthode unique qui permet au consommateur de faire un rapport sur le service avec un paramètre lifetimeDetails donné. Lorsqu’elle est appelée, la méthode ReportServiceLifetimeDetails consigne 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. Il est recommandé que les applications suivent cette convention. 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 :

  • S’affichent dans IntelliSense sans nécessiter de blocs using supplémentaires.
  • Réduisent le nombre d’instructions using requises dans les classes Program ou Startup dans lesquelles ces méthodes d’extension sont généralement appelées.

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é à une ServiceLifetime. L’exemple d’application montre comment inscrire des implémentations de service sur une interface et comment inscrire des classes pures sans interfaces de support. 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 constater ce qui suit :

  • Les services Transient sont toujours différents, une nouvelle instance est créée avec chaque récupération du service.
  • Les services Scoped changent uniquement avec une nouvelle étendue, mais constituent la même instance dans une étendue.
  • Les services Singleton sont toujours identiques, une nouvelle instance n’est créée qu’une seule fois.

Voir aussi