Partager via


Comprendre les bases de l’injection de dépendances dans .NET

Dans cet article, vous allez créer une application console .NET qui crée manuellement un ServiceCollection et le ServiceProvidercorrespondant. Vous apprendrez à inscrire des services et à les résoudre à l’aide de l’injection de dépendances (DI). Cet article utilise le package NuGet Microsoft.Extensions.DependencyInjection pour démontrer les bases de l’injection de dépendances dans .NET.

Remarque

Cet article ne tire pas parti des fonctionnalités d’hôte générique. Pour obtenir un guide plus complet, consultez Utiliser l’injection de dépendances dans .NET.

Bien démarrer

Pour commencer, créez une application console .NET nommée DI.Basics. La liste suivante référence quelques-unes des approches les plus courantes pour créer un projet de console :

Vous devez ajouter la référence de package à Microsoft.Extensions.DependencyInjection dans le fichier projet. Quelle que soit l’approche que vous choisissez, vérifiez que le projet ressemble au code XML suivant du fichier DI.Basics.csproj :

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
  </ItemGroup>

</Project>

Bases de l’injection de dépendances

L’injection de dépendances est un modèle de conception qui vous permet non seulement de supprimer les dépendances codées en dur, mais aussi de gérer et de tester plus facilement votre application. L’injection de dépendances est une technique permettant d’atteindre l’inversion de contrôle (IoC) entre les classes et leurs dépendances.

Les abstractions pour l’injection de dépendances dans .NET sont définies dans le package NuGet Microsoft.Extensions.DependencyInjection.Abstractions :

  • IServiceCollection : définit un contrat pour une collection de descripteurs de service.
  • IServiceProvider : définit un mécanisme de récupération d’un objet de service.
  • ServiceDescriptor : décrit un service avec son type de service, son implémentation et sa durée de vie.

Dans .NET, vous gérez l’injection de dépendances en ajoutant des services et en les configurant dans un IServiceCollection. Une fois les services inscrits, une instance IServiceProvider est générée en appelant la méthode BuildServiceProvider. IServiceProvider sert de conteneur pour tous les services inscrits et est utilisé pour résoudre les services.

Créez des services example

Tous les services ne sont pas créés de la même manière. Certains services nécessitent une nouvelle instance à chaque fois que le conteneur de services les obtient (transient), tandis que d'autres doivent être partagés entre plusieurs requêtes (scoped) ou pour toute la durée de vie de l'application (singleton). Pour plus d’informations, consultez Durées de vie des services.

De même, certains services n’exposent qu’un type concret, tandis que d’autres sont exprimés sous la forme d’un contrat entre une interface et un type d’implémentation. Vous allez créer plusieurs variantes de services pour illustrer ces concepts.

Créez un fichier C# nommé IConsole.cs, puis ajoutez le code suivant :

public interface IConsole
{
    void WriteLine(string message);
}

Ce fichier définit une interface IConsole qui expose une méthode unique, WriteLine. Créez ensuite un fichier C# nommé DefaultConsole.cs, puis ajoutez le code suivant :

internal sealed class DefaultConsole : IConsole
{
    public bool IsEnabled { get; set; } = true;

    void IConsole.WriteLine(string message)
    {
        if (IsEnabled is false)
        {
            return;
        }

        Console.WriteLine(message);
    }
}

Le code précédent représente l’implémentation par défaut de l’interface IConsole. La méthode WriteLine écrit de manière conditionnelle dans la console en fonction de la propriété IsEnabled.

Conseil

Votre équipe de développement doit se mettre d’accord sur le nom à attribuer à l’implémentation. Le préfixe Default est une convention courante pour indiquer une implémentation par défaut d’une interface, mais il n’est pas obligatoire.

Créez ensuite un fichier IGreetingService.cs, puis ajoutez le code C# suivant :

public interface IGreetingService
{
    string Greet(string name);
}

Ajoutez ensuite un nouveau fichier C# nommé DefaultGreetingService.cs et ajoutez le code suivant :

internal sealed class DefaultGreetingService(
    IConsole console) : IGreetingService
{
    public string Greet(string name)
    {
        var greeting = $"Hello, {name}!";

        console.WriteLine(greeting);

        return greeting;
    }
}

Le code précédent représente l’implémentation par défaut de l’interface IGreetingService. L’implémentation du service nécessite un IConsole en tant que paramètre de constructeur principal. La méthode Greet :

  • Crée un greeting en fonction de name.
  • Appelle la méthode WriteLine sur l’instance IConsole.
  • Retourne le greeting à l’appelant.

Le dernier service à créer est le fichier FarewellService.cs. Ajoutez le code C# suivant avant de continuer :

public class FarewellService(IConsole console)
{
    public string SayGoodbye(string name)
    {
        var farewell = $"Goodbye, {name}!";

        console.WriteLine(farewell);

        return farewell;
    }
}

FarewellService représente un type concret, pas une interface. Il doit être déclaré comme public pour le rendre accessible aux consommateurs. Contrairement à d’autres types d’implémentation de service déclarés comme internal et sealed, ce code montre que tous les services ne doivent pas nécessairement être des interfaces. Il montre également que les implémentations de service peuvent être sealed pour empêcher l’héritage et internal pour restreindre l’accès à l’assembly.

Mettre à jour la classe Program

Ouvrez le fichier Program.cs et remplacez le code existant par le code C# suivant :

using Microsoft.Extensions.DependencyInjection;

// 1. Create the service collection.
var services = new ServiceCollection();

// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    });
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();

// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();

// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();

// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");

Le code mis à jour précédent illustre la procédure à suivre :

  • Créez une instance ServiceCollection.
  • Inscrivez et configurez des services dans ServiceCollection :
    • IConsole utilisant la surcharge de la fabrique d’implémentation, retournez un type DefaultConsole avec IsEnabled défini sur true.
    • IGreetingService est ajouté avec un type d’implémentation correspondant de type DefaultGreetingService.
    • FarewellService est ajouté en tant que type concret.
  • Générez le ServiceProvider à partir de ServiceCollection.
  • Résolvez les services IGreetingService et FarewellService.
  • Utilisez les services résolus pour accueillir et dire au revoir à une personne nommée David.

Si vous mettez à jour la propriété IsEnabled de DefaultConsole sur false, les méthodes Greet et SayGoodbye omettent d’écrire dans les messages résultants sur la console. Un changement comme celui-ci permet de démontrer que le service IConsole est injecté dans les services IGreetingService et FarewellService en tant que dépendance qui influence le comportement de cette application.

Tous ces services sont enregistrés en tant que singletons, bien que pour cet exemple, ils fonctionnent de manière identique s'ils sont enregistrés en tant que services transient ou scoped.

Important

Dans ce cas de figure example, les durées de vie des services n'ont pas d'importance, mais dans une application réelle, vous devriez examiner attentivement la durée de vie de chaque service.

Exécution de l'exemple d'application

Pour exécuter l’exemple d’application, appuyez sur F5 dans Visual Studio ou Visual Studio Code ou exécutez la commande dotnet run dans le terminal. Une fois l’application exécutée, le résultat suivant doit s’afficher :

Hello, David!
Goodbye, David!

Descripteurs de service

Les API les plus couramment utilisées pour ajouter des services à ServiceCollection sont des méthodes d’extension génériques nommées à vie, notamment :

  • AddSingleton<TService>
  • AddTransient<TService>
  • AddScoped<TService>

Ces méthodes sont des méthodes pratiques qui créent une instance de ServiceDescriptor et l’ajoutent à ServiceCollection. ServiceDescriptor est une classe simple qui décrit un service avec son type de service, son type d’implémentation et sa durée de vie. Cette classe peut également décrire les fabriques et instances d’implémentation.

Pour chacun des services que vous avez inscrits dans ServiceCollection, vous pouvez appeler directement la méthode Add avec une instance de ServiceDescriptor. Penchez-vous sur les exemples suivants :

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IConsole),
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    },
    lifetime: ServiceLifetime.Singleton));

Le code précédent est équivalent à la façon dont le service IConsole a été inscrit dans ServiceCollection. La méthode Add est utilisée pour ajouter une instance de ServiceDescriptor qui décrit le service IConsole. La méthode statique ServiceDescriptor.Describe délègue à différents constructeurs ServiceDescriptor. Considérez le code équivalent pour le service IGreetingService :

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IGreetingService),
    implementationType: typeof(DefaultGreetingService),
    lifetime: ServiceLifetime.Singleton));

Le code précédent décrit le service IGreetingService avec son type de service, son type d’implémentation et sa durée de vie. Enfin, considérez le code équivalent pour le service FarewellService :

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(FarewellService),
    implementationType: typeof(FarewellService),
    lifetime: ServiceLifetime.Singleton));

Le code précédent décrit le type concret FarewellService en tant que type de service et type d’implémentation. Le service est enregistré en tant que service singleton.

Voir aussi