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 :
- Visual Studio : menu Fichier > Nouveau > Projet.
- Visual Studio Code et extension C# Dev Kit : option de menu Explorateur de solutions.
- Interface de ligne de commande .NET : commande
dotnet new console
dans le terminal.
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="9.0.1" />
</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éer des exemples de services
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 (transitoires), tandis que d’autres doivent être partagés entre les requêtes (délimités) ou pendant toute la durée de vie de l’application (singletons). 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 dename
. - Appelle la méthode
WriteLine
sur l’instanceIConsole
. - 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
:- Le
IConsole
utilisant la surcharge de la fabrique d’implémentation, retournez un typeDefaultConsole
avecIsEnabled
défini surtrue
. IGreetingService
est ajouté avec un type d’implémentation correspondant de typeDefaultGreetingService
.FarewellService
est ajouté en tant que type concret.
- Le
- Générez le
ServiceProvider
à partir deServiceCollection
. - Résolvez les services
IGreetingService
etFarewellService
. - 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 inscrits en tant que singletons. Toutefois pour cet exemple, ils fonctionnent comme s’ils étaient inscrits en tant que services transitoires ou délimités.
Important
Dans cet exemple fictif, les durées de vie des services n’ont pas d’importance. En revanche, dans une application réelle, vous devez 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 inscrit en tant que service singleton.