Injection de dépendances dans ASP.NET Core
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 8 de cet article.
Par Kirk Larkin, Steve Smith et Brandon Dahler
ASP.NET Core 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.
Pour plus d’informations sur l’injection de dépendances dans les contrôleurs MVC, consultez Injection de dépendances dans les contrôleurs dans ASP.NET Core.
Pour obtenir des informations sur l’utilisation de l’injection de dépendances dans des applications autres que des applications web, consultez Injection de dépendances dans .NET.
Pour plus d’informations sur l’injection de dépendances d’options, consultez Modèle d’options dans ASP.NET Core.
Cette rubrique fournit des informations sur l’injection de dépendances dans ASP.NET Core. La documentation principale sur l’utilisation de l’injection de dépendances est incluse dans Injection de dépendances dans .NET.
Pour obtenir des instructions sur l’injection de dépendance Blazor, en complément ou en remplacement des instructions de cet article, consultez Injection de dépendance Blazor ASP.NET Core.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Vue d’ensemble de l’injection de dépendances
Une dépendance est un objet dont dépend un autre objet. Examinez la classe MyDependency
suivante avec une méthode WriteMessage
dont dépendent d’autres classes :
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Une classe peut créer une instance de la classe MyDependency
pour utiliser sa méthode WriteMessage
. Dans l’exemple suivant, la classe MyDependency
est une dépendance de la classe IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
La classe est créee et dépend directement de la classe MyDependency
. Les dépendances de code, comme dans l’exemple précédent, posent problème et doivent être évitées pour les raisons suivantes :
- Pour remplacer
MyDependency
par une autre implémentation, la classeIndexModel
doit être modifiée. - Si
MyDependency
possède des dépendances, elles doivent être configurées par la classeIndexModel
. Dans un grand projet comportant plusieurs classes dépendant deMyDependency
, le code de configuration est disséminé dans l’application. - Cette implémentation complique le test unitaire.
L’injection de dépendances résout ces problèmes via :
- 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. ASP.NET Core fournit un conteneur de service intégré, IServiceProvider. Les services sont généralement inscrits dans le fichier
Program.cs
de l’application. - 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.
Dans l’exemple d’application, l’interface IMyDependency
définit la méthode WriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Cette interface est implémentée par un type concret, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L’exemple d’application inscrit le service IMyDependency
avec le type MyDependency
concret. La méthode AddScoped inscrit le service avec une durée de vie délimitée, la durée de vie d’une requête unique. Les durées de vie du service sont décrites plus loin dans cette rubrique.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
Dans l’exemple d’application, le service IMyDependency
est demandé et utilisé pour appeler la méthode WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
En utilisant le modèle d’injection de dépendances, le contrôleur ou la page Razor :
- N’utilise pas le type
MyDependency
concret, uniquement l’interfaceIMyDependency
qu’il implémente. Cela facilite la modification de l’implémentation sans modifier le contrôleur ou la page Razor. - Ne crée pas d’instance de
MyDependency
; elle est créée par le conteneur d’injection de dépendances.
L’implémentation de l’interface IMyDependency
peut être améliorée à l’aide de l’API de journalisation intégrée :
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Le fichier Program.cs
mis à jour inscrit la nouvelle implémentation IMyDependency
:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
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 qui doivent être résolues est généralement appelé arborescence des dépendances, graphique de dépendance 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).
Dans 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
IMyDependency
. - 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 implémentations IMyDependency
indiquées dans les exemples précédents ont été écrites pour démontrer l’injection de dépendances de base, et non pour implémenter 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 ne nécessite l’inscription d’aucun service :
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
En utilisant le code précédent, il n’est pas nécessaire de mettre à jour Program.cs
, car la journalisation est fournie par l’infrastructure.
Inscrire des groupes de services avec des méthodes d’extension
L’infrastructure ASP.NET Core utilise 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 AddControllers inscrit les services requis pour les contrôleurs MVC.
Le code suivant est généré par le modèle des pages Razor à l’aide de comptes d’utilisateur individuels et montre comment ajouter des services supplémentaires au conteneur à l’aide des méthodes d’extension AddDbContext et AddDefaultIdentity :
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Examinons les éléments suivants permettant d’inscrire les services et de configurer les options :
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
var app = builder.Build();
Les groupes d’inscriptions associés peuvent être déplacés vers une méthode d’extension pour inscrire les services. Les services de configuration sont par exemple ajoutés à la classe suivante :
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Les services qui restent sont inscrits dans une classe similaire. Le code suivant utilise les nouvelles méthodes d’extension pour inscrire les services :
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Remarque : chaque méthode d’extension services.Add{GROUP_NAME}
ajoute des services et les configure éventuellement. Par exemple, AddControllersWithViews ajoute les services nécessaires à MVC avec vues et AddRazorPages ajoute les services nécessaires aux pages Razor.
Durées de service
Consultez Durées de service dans Injection de dépendances dans .NET
Pour utiliser des services délimités dans un intergiciel, utilisez l’une des approches suivantes :
- Injectez le service dans la méthode
Invoke
ouInvokeAsync
de l’intergiciel. L’utilisation de l’injection de constructeur lève une exception de runtime, car elle force le service délimité à se comporter comme un singleton. L’exemple utilisé dans la section Options de durée de vie et d’inscription illustre l’approcheInvokeAsync
. - Utilisez un intergiciel basé sur une fabrique. L’intergiciel inscrit à l’aide de cette approche est activé par demande client (connexion), ce qui permet d’injecter des services délimités dans le constructeur de l’intergiciel.
Pour plus d’informations, consultez Écrire un intergiciel ASP.NET Core personnalisé.
Méthodes d’inscription du service
Consultez Méthodes d’inscription de service dans Injection de dépendances dans .NET
Il est courant d’utiliser plusieurs implémentations lors d’une simulation de types à des fins de test.
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. C’est pourquoi plusieurs implémentations d’un service ne peuvent pas être inscrites à l’aide des méthodes qui n’acceptent pas de type de service explicite. Ces méthodes peuvent inscrire plusieurs instances d’un service, mais elles auront toutes le même type d’implémentation.
L’une des méthodes d’inscription de service ci-dessus peut être utilisée pour inscrire plusieurs instances de service du même type de service. Dans l’exemple suivant, AddSingleton
est appelé deux fois avec IMyDependency
comme type de service. Le deuxième appel à AddSingleton
remplace le précédent lorsqu’il est résolu en tant que IMyDependency
et s’ajoute au précédent lorsque plusieurs services sont résolus via IEnumerable<IMyDependency>
. Les services apparaissent dans l’ordre dans lequel ils ont été inscrits lorsqu’ils sont résolus via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Services à clé
Les services à clé désigne un mécanisme d’inscription et de récupération des services d’injection de dépendances (DI) à l’aide de clés. Un service est associé à une clé en appelant AddKeyedSingleton (ou AddKeyedScoped
ou AddKeyedTransient
) pour l’inscrire. Accédez à un service inscrit en spécifiant la clé avec l’attribut [FromKeyedServices]
. Le code suivant montre comment utiliser les services à clé :
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
Services clés dans l’intergiciel
L’intergiciel prend en charge les services à clé dans le constructeur et la méthode Invoke
/InvokeAsync
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");
var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();
internal class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next,
[FromKeyedServices("test")] MySingletonClass service)
{
_next = next;
}
public Task Invoke(HttpContext context,
[FromKeyedServices("test2")]
MyScopedClass scopedService) => _next(context);
}
Pour plus d’informations sur la création d’intergiciel, consultez Écrire un intergiciel ASP.NET Core personnalisé.
Comportement d’injection de constructeurs
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Contextes Entity Framework
Par défaut, les contextes Entity Framework sont ajoutés au conteneur de service en utilisant la durée de vie limitée, car la portée des opérations de base de données d’application web est normalement limitée à la demande du client. Pour utiliser une durée de vie différente, spécifiez la durée de vie à l’aide d’une surcharge AddDbContext. Les services d’une durée de vie donnée ne doivent pas utiliser un contexte de base de données dont la durée de vie est plus courte que celle du service.
Options de durée de vie et d’inscription
Pour illustrer la différence entre les options de durée de vie et d’inscription de service, considérez les interfaces suivantes qui représentent une tâche en tant qu’opération avec un identificateur, OperationId
. Selon la façon dont la durée de vie d’un service d’opérations est configurée pour les interfaces suivantes, le conteneur fournit la même instance ou une instance différente du service lorsqu’une classe le demande :
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
La classe Operation
suivante implémente toutes les interfaces précédentes. Le constructeur Operation
génère un GUID et stocke les 4 derniers caractères dans la propriété OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Le code suivant crée plusieurs inscriptions de la classe Operation
en fonction des durées de vie nommées :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
L’exemple d’application illustre les durées de vie des objets au sein et entre des demandes. IndexModel
et l’intergiciel demandent chaque type de IOperation
et consignent OperationId
pour chacun :
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
Comme IndexModel
, l’intergiciel résout les mêmes services :
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Les services délimités et temporaires doivent être résolus dans la méthode InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
La sortie de l’enregistreur d’événements montre :
- Les objets Transient sont toujours différents. La valeur
OperationId
temporaire est différente dansIndexModel
et dans l’intergiciel. - Les objets délimités sont les mêmes pour une demande donnée, mais diffèrent pour chaque nouvelle demande.
- Les objets singleton sont identiques pour chaque demande.
Pour réduire la sortie de journalisation, définissez « Logging:LogLevel:Microsoft:Error » dans le fichier appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Résoudre un service au démarrage de l’application
Le code suivant montre comment résoudre un service délimité pendant une durée limitée quand l’application démarre :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
app.MapGet("/", () => "Hello World!");
app.Run();
Validation de l’étendue
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Pour plus d’informations, consultez Validation de l’étendue.
Services de requête
Les services et leurs dépendances au sein d’une demande ASP.NET Core sont exposés via HttpContext.RequestServices.
L’infrastructure crée une étendue par demande et RequestServices
expose le fournisseur de services délimité. Tous les services délimités sont valides tant que la demande est active.
Remarque
Préférez demander des dépendances en tant que paramètres de constructeur plutôt que résoudre les services à partir de RequestServices
. La demande de dépendances en tant que paramètres de constructeur génère des classes plus faciles à tester.
Conception de services pour l’injection de dépendances
Lors de la conception de services pour l’injection de dépendances :
- Évitez les classes et les membres statiques avec état. Évitez de créer un état global en concevant des applications pour utiliser des services singleton à la place.
- Éviter une instanciation directe de classes dépendantes au sein de services. L’instanciation directe associe le code à une implémentation particulière.
- Limitez la taille des services, faites en sorte qu’elles soient bien factorisées et facilement testées.
Si une classe possède de nombreuses dépendances injectées, il peut s’agir d’un signe que la classe a trop de responsabilités et viole le principe de responsabilité unique (SRP). Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans de nouvelles classes. N’oubliez pas que les classes du modèle de page Razor Pages et les classes du contrôleur MVC doivent se concentrer sur les problèmes d’interface utilisateur.
Suppression des services
Le conteneur appelle Dispose pour les types IDisposable qu’il crée. Les services résolus à partir du conteneur ne doivent jamais être supprimés par le développeur. Si un type ou une fabrique est inscrit en tant que singleton, le conteneur supprime automatiquement le singleton.
Dans l’exemple suivant, les services sont créés par le conteneur de service et supprimés automatiquement : dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
La console de débogage affiche la sortie suivante après chaque actualisation de la page Index :
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Services non créés par le conteneur de services
Prenez le code suivant :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
Dans le code précédent :
- Les instances de service ne sont pas créées par le conteneur de service.
- L’infrastructure ne supprime pas automatiquement les services.
- Le développeur est responsable de la suppression des services.
Conseils sur l’interface IDisposable pour les instances temporaires et partagées
Consultez Conseils sur l’interface IDisposable pour les instances temporaires et partagées dans Injection de dépendances dans .NET
Remplacement de conteneur de services par défaut
Consultez Remplacement de conteneur de services par défaut dans Injection de dépendances dans .NET
Recommandations
Consultez Recommandations dans Injection de dépendances dans .NET
Évitez d’utiliser le modèle de localisateur de service. Par exemple, n’appelez pas GetService pour obtenir une instance de service si vous pouvez utiliser l’injection de dépendance à la place :
Incorrect :
Correct :
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Une autre variante du localisateur de service à éviter est l’injection d’une fabrique qui résout les dépendances au moment de l’exécution. Ces deux pratiques combinent des stratégies Inversion de contrôle.
Évitez l’accès statique à
HttpContext
(par exemple, IHttpContextAccessor.HttpContext).
L’injection de dépendance constitue une alternative aux modèles d’accès aux objets statiques/globaux. Il est possible que vous ne bénéficiez pas des avantages de l’injection de dépendances si vous la combinez avec l’accès aux objets statiques.
Modèles recommandés pour la multilocation dans l’injection de dépendances
Orchard Core est une infrastructure d’application permettant de créer des applications modulaires multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation Orchard Core.
Consultez les exemples Orchard Core pour obtenir des exemples de création d’applications modulaires et multilocataires utilisant simplement l’infrastructure Orchard Core sans aucune de ses fonctionnalités spécifiques à CMS.
Services fournis par le framework
Program.cs
inscrit les services utilisés par l’application, notamment les fonctionnalités de plateforme comme Entity Framework Core et ASP.NET Core MVC. Au départ, IServiceCollection
fournie à Program.cs
a les services définis par l’infrastructure en fonction de la manière dont l’hôte a été configuré. Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure inscrit plus de 250 services.
Le tableau suivant répertorie un petit exemple de ces services inscrits dans l’infrastructure :
Type de service | Durée de vie |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Temporaire |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Temporaire |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Temporaire |
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.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ressources supplémentaires
- Injection de dépendances dans les vues dans ASP.NET Core
- Injection de dépendances dans les contrôleurs dans ASP.NET Core
- Injection de dépendances dans les gestionnaires d’exigences dans ASP.NET Core
- Injection de dépendances ASP.NET Core Blazor
- NDC Conference Patterns for DI app development
- Démarrage d’une application dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Comprendre les bases de l’injection de dépendances dans .NET
- Recommandations relatives à l’injection de dépendances
- Didacticiel : Utiliser l’injection de dépendances dans .NET
- Injection de dépendances .NET
- INJECTION DE DÉPENDANCES ASP.NET CORE : QU’EST-CE QUE ISERVICECOLLECTION ?
- Quatre façons de supprimer les IDisposables dans ASP.NET Core
- Écrire un code clair dans ASP.NET Core avec l’injection de dépendance (MSDN)
- Principe des dépendances explicites
- Conteneurs d’inversion de contrôle et modèle d’injection de dépendances (Martin Fowler)
- Comment inscrire un service avec plusieurs interfaces dans l’injection de dépendance ASP.NET Core
Par Kirk Larkin, Steve Smith et Brandon Dahler
ASP.NET Core 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.
Pour plus d’informations sur l’injection de dépendances dans les contrôleurs MVC, consultez Injection de dépendances dans les contrôleurs dans ASP.NET Core.
Pour obtenir des informations sur l’utilisation de l’injection de dépendances dans des applications autres que des applications web, consultez Injection de dépendances dans .NET.
Pour plus d’informations sur l’injection de dépendances d’options, consultez Modèle d’options dans ASP.NET Core.
Cette rubrique fournit des informations sur l’injection de dépendances dans ASP.NET Core. La documentation principale sur l’utilisation de l’injection de dépendances est incluse dans Injection de dépendances dans .NET.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Vue d’ensemble de l’injection de dépendances
Une dépendance est un objet dont dépend un autre objet. Examinez la classe MyDependency
suivante avec une méthode WriteMessage
dont dépendent d’autres classes :
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Une classe peut créer une instance de la classe MyDependency
pour utiliser sa méthode WriteMessage
. Dans l’exemple suivant, la classe MyDependency
est une dépendance de la classe IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
La classe est créee et dépend directement de la classe MyDependency
. Les dépendances de code, comme dans l’exemple précédent, posent problème et doivent être évitées pour les raisons suivantes :
- Pour remplacer
MyDependency
par une autre implémentation, la classeIndexModel
doit être modifiée. - Si
MyDependency
possède des dépendances, elles doivent être configurées par la classeIndexModel
. Dans un grand projet comportant plusieurs classes dépendant deMyDependency
, le code de configuration est disséminé dans l’application. - Cette implémentation complique le test unitaire.
L’injection de dépendances résout ces problèmes via :
- 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. ASP.NET Core fournit un conteneur de service intégré, IServiceProvider. Les services sont généralement inscrits dans le fichier
Program.cs
de l’application. - 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.
Dans l’exemple d’application, l’interface IMyDependency
définit la méthode WriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Cette interface est implémentée par un type concret, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L’exemple d’application inscrit le service IMyDependency
avec le type MyDependency
concret. La méthode AddScoped inscrit le service avec une durée de vie délimitée, la durée de vie d’une requête unique. Les durées de vie du service sont décrites plus loin dans cette rubrique.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
Dans l’exemple d’application, le service IMyDependency
est demandé et utilisé pour appeler la méthode WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
En utilisant le modèle d’injection de dépendances, le contrôleur ou la page Razor :
- N’utilise pas le type
MyDependency
concret, uniquement l’interfaceIMyDependency
qu’il implémente. Cela facilite la modification de l’implémentation sans modifier le contrôleur ou la page Razor. - Ne crée pas d’instance de
MyDependency
; elle est créée par le conteneur d’injection de dépendances.
L’implémentation de l’interface IMyDependency
peut être améliorée à l’aide de l’API de journalisation intégrée :
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Le fichier Program.cs
mis à jour inscrit la nouvelle implémentation IMyDependency
:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
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 qui doivent être résolues est généralement appelé arborescence des dépendances, graphique de dépendance 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).
Dans 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
IMyDependency
. - 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 implémentations IMyDependency
indiquées dans les exemples précédents ont été écrites pour démontrer l’injection de dépendances de base, et non pour implémenter 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 ne nécessite l’inscription d’aucun service :
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
En utilisant le code précédent, il n’est pas nécessaire de mettre à jour Program.cs
, car la journalisation est fournie par l’infrastructure.
Inscrire des groupes de services avec des méthodes d’extension
L’infrastructure ASP.NET Core utilise 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 AddControllers inscrit les services requis pour les contrôleurs MVC.
Le code suivant est généré par le modèle des pages Razor à l’aide de comptes d’utilisateur individuels et montre comment ajouter des services supplémentaires au conteneur à l’aide des méthodes d’extension AddDbContext et AddDefaultIdentity :
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Examinons les éléments suivants permettant d’inscrire les services et de configurer les options :
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
var app = builder.Build();
Les groupes d’inscriptions associés peuvent être déplacés vers une méthode d’extension pour inscrire les services. Les services de configuration sont par exemple ajoutés à la classe suivante :
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Les services qui restent sont inscrits dans une classe similaire. Le code suivant utilise les nouvelles méthodes d’extension pour inscrire les services :
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Remarque : chaque méthode d’extension services.Add{GROUP_NAME}
ajoute des services et les configure éventuellement. Par exemple, AddControllersWithViews ajoute les services nécessaires à MVC avec vues et AddRazorPages ajoute les services nécessaires aux pages Razor.
Durées de service
Consultez Durées de service dans Injection de dépendances dans .NET
Pour utiliser des services délimités dans un intergiciel, utilisez l’une des approches suivantes :
- Injectez le service dans la méthode
Invoke
ouInvokeAsync
de l’intergiciel. L’utilisation de l’injection de constructeur lève une exception de runtime, car elle force le service délimité à se comporter comme un singleton. L’exemple utilisé dans la section Options de durée de vie et d’inscription illustre l’approcheInvokeAsync
. - Utilisez un intergiciel basé sur une fabrique. L’intergiciel inscrit à l’aide de cette approche est activé par demande client (connexion), ce qui permet d’injecter des services délimités dans le constructeur de l’intergiciel.
Pour plus d’informations, consultez Écrire un intergiciel ASP.NET Core personnalisé.
Méthodes d’inscription du service
Consultez Méthodes d’inscription de service dans Injection de dépendances dans .NET
Il est courant d’utiliser plusieurs implémentations lors d’une simulation de types à des fins de test.
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. C’est pourquoi plusieurs implémentations d’un service ne peuvent pas être inscrites à l’aide des méthodes qui n’acceptent pas de type de service explicite. Ces méthodes peuvent inscrire plusieurs instances d’un service, mais elles auront toutes le même type d’implémentation.
L’une des méthodes d’inscription de service ci-dessus peut être utilisée pour inscrire plusieurs instances de service du même type de service. Dans l’exemple suivant, AddSingleton
est appelé deux fois avec IMyDependency
comme type de service. Le deuxième appel à AddSingleton
remplace le précédent lorsqu’il est résolu en tant que IMyDependency
et s’ajoute au précédent lorsque plusieurs services sont résolus via IEnumerable<IMyDependency>
. Les services apparaissent dans l’ordre dans lequel ils ont été inscrits lorsqu’ils sont résolus via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Services à clé
Les services à clé désigne un mécanisme d’inscription et de récupération des services d’injection de dépendances (DI) à l’aide de clés. Un service est associé à une clé en appelant AddKeyedSingleton (ou AddKeyedScoped
ou AddKeyedTransient
) pour l’inscrire. Accédez à un service inscrit en spécifiant la clé avec l’attribut [FromKeyedServices]
. Le code suivant montre comment utiliser les services à clé :
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
Comportement d’injection de constructeurs
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Contextes Entity Framework
Par défaut, les contextes Entity Framework sont ajoutés au conteneur de service en utilisant la durée de vie limitée, car la portée des opérations de base de données d’application web est normalement limitée à la demande du client. Pour utiliser une durée de vie différente, spécifiez la durée de vie à l’aide d’une surcharge AddDbContext. Les services d’une durée de vie donnée ne doivent pas utiliser un contexte de base de données dont la durée de vie est plus courte que celle du service.
Options de durée de vie et d’inscription
Pour illustrer la différence entre les options de durée de vie et d’inscription de service, considérez les interfaces suivantes qui représentent une tâche en tant qu’opération avec un identificateur, OperationId
. Selon la façon dont la durée de vie d’un service d’opérations est configurée pour les interfaces suivantes, le conteneur fournit la même instance ou une instance différente du service lorsqu’une classe le demande :
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
La classe Operation
suivante implémente toutes les interfaces précédentes. Le constructeur Operation
génère un GUID et stocke les 4 derniers caractères dans la propriété OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Le code suivant crée plusieurs inscriptions de la classe Operation
en fonction des durées de vie nommées :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
L’exemple d’application illustre les durées de vie des objets au sein et entre des demandes. IndexModel
et l’intergiciel demandent chaque type de IOperation
et consignent OperationId
pour chacun :
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
Comme IndexModel
, l’intergiciel résout les mêmes services :
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Les services délimités et temporaires doivent être résolus dans la méthode InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
La sortie de l’enregistreur d’événements montre :
- Les objets Transient sont toujours différents. La valeur
OperationId
temporaire est différente dansIndexModel
et dans l’intergiciel. - Les objets délimités sont les mêmes pour une demande donnée, mais diffèrent pour chaque nouvelle demande.
- Les objets singleton sont identiques pour chaque demande.
Pour réduire la sortie de journalisation, définissez « Logging:LogLevel:Microsoft:Error » dans le fichier appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Résoudre un service au démarrage de l’application
Le code suivant montre comment résoudre un service délimité pendant une durée limitée quand l’application démarre :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
app.MapGet("/", () => "Hello World!");
app.Run();
Validation de l’étendue
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Pour plus d’informations, consultez Validation de l’étendue.
Services de requête
Les services et leurs dépendances au sein d’une demande ASP.NET Core sont exposés via HttpContext.RequestServices.
L’infrastructure crée une étendue par demande et RequestServices
expose le fournisseur de services délimité. Tous les services délimités sont valides tant que la demande est active.
Remarque
Préférez demander des dépendances en tant que paramètres de constructeur plutôt que résoudre les services à partir de RequestServices
. La demande de dépendances en tant que paramètres de constructeur génère des classes plus faciles à tester.
Conception de services pour l’injection de dépendances
Lors de la conception de services pour l’injection de dépendances :
- Évitez les classes et les membres statiques avec état. Évitez de créer un état global en concevant des applications pour utiliser des services singleton à la place.
- Éviter une instanciation directe de classes dépendantes au sein de services. L’instanciation directe associe le code à une implémentation particulière.
- Limitez la taille des services, faites en sorte qu’elles soient bien factorisées et facilement testées.
Si une classe possède de nombreuses dépendances injectées, il peut s’agir d’un signe que la classe a trop de responsabilités et viole le principe de responsabilité unique (SRP). Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans de nouvelles classes. N’oubliez pas que les classes du modèle de page Razor Pages et les classes du contrôleur MVC doivent se concentrer sur les problèmes d’interface utilisateur.
Suppression des services
Le conteneur appelle Dispose pour les types IDisposable qu’il crée. Les services résolus à partir du conteneur ne doivent jamais être supprimés par le développeur. Si un type ou une fabrique est inscrit en tant que singleton, le conteneur supprime automatiquement le singleton.
Dans l’exemple suivant, les services sont créés par le conteneur de service et supprimés automatiquement : dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
La console de débogage affiche la sortie suivante après chaque actualisation de la page Index :
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Services non créés par le conteneur de services
Prenez le code suivant :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
Dans le code précédent :
- Les instances de service ne sont pas créées par le conteneur de service.
- L’infrastructure ne supprime pas automatiquement les services.
- Le développeur est responsable de la suppression des services.
Conseils sur l’interface IDisposable pour les instances temporaires et partagées
Consultez Conseils sur l’interface IDisposable pour les instances temporaires et partagées dans Injection de dépendances dans .NET
Remplacement de conteneur de services par défaut
Consultez Remplacement de conteneur de services par défaut dans Injection de dépendances dans .NET
Recommandations
Consultez Recommandations dans Injection de dépendances dans .NET
Évitez d’utiliser le modèle de localisateur de service. Par exemple, n’appelez pas GetService pour obtenir une instance de service si vous pouvez utiliser l’injection de dépendance à la place :
Incorrect :
Correct :
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Une autre variante du localisateur de service à éviter est l’injection d’une fabrique qui résout les dépendances au moment de l’exécution. Ces deux pratiques combinent des stratégies Inversion de contrôle.
Évitez l’accès statique à
HttpContext
(par exemple, IHttpContextAccessor.HttpContext).
L’injection de dépendance constitue une alternative aux modèles d’accès aux objets statiques/globaux. Il est possible que vous ne bénéficiez pas des avantages de l’injection de dépendances si vous la combinez avec l’accès aux objets statiques.
Modèles recommandés pour la multilocation dans l’injection de dépendances
Orchard Core est une infrastructure d’application permettant de créer des applications modulaires multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation Orchard Core.
Consultez les exemples Orchard Core pour obtenir des exemples de création d’applications modulaires et multilocataires utilisant simplement l’infrastructure Orchard Core sans aucune de ses fonctionnalités spécifiques à CMS.
Services fournis par le framework
Program.cs
inscrit les services utilisés par l’application, notamment les fonctionnalités de plateforme comme Entity Framework Core et ASP.NET Core MVC. Au départ, IServiceCollection
fournie à Program.cs
a les services définis par l’infrastructure en fonction de la manière dont l’hôte a été configuré. Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure inscrit plus de 250 services.
Le tableau suivant répertorie un petit exemple de ces services inscrits dans l’infrastructure :
Type de service | Durée de vie |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Temporaire |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Temporaire |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Temporaire |
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.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ressources supplémentaires
- Injection de dépendances dans les vues dans ASP.NET Core
- Injection de dépendances dans les contrôleurs dans ASP.NET Core
- Injection de dépendances dans les gestionnaires d’exigences dans ASP.NET Core
- Injection de dépendances ASP.NET Core Blazor
- NDC Conference Patterns for DI app development
- Démarrage d’une application dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Comprendre les bases de l’injection de dépendances dans .NET
- Recommandations relatives à l’injection de dépendances
- Didacticiel : Utiliser l’injection de dépendances dans .NET
- Injection de dépendances .NET
- INJECTION DE DÉPENDANCES ASP.NET CORE : QU’EST-CE QUE ISERVICECOLLECTION ?
- Quatre façons de supprimer les IDisposables dans ASP.NET Core
- Écrire un code clair dans ASP.NET Core avec l’injection de dépendance (MSDN)
- Principe des dépendances explicites
- Conteneurs d’inversion de contrôle et modèle d’injection de dépendances (Martin Fowler)
- Comment inscrire un service avec plusieurs interfaces dans l’injection de dépendance ASP.NET Core
Par Kirk Larkin, Steve Smith et Brandon Dahler
ASP.NET Core 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.
Pour plus d’informations sur l’injection de dépendances dans les contrôleurs MVC, consultez Injection de dépendances dans les contrôleurs dans ASP.NET Core.
Pour obtenir des informations sur l’utilisation de l’injection de dépendances dans des applications autres que des applications web, consultez Injection de dépendances dans .NET.
Pour plus d’informations sur l’injection de dépendances d’options, consultez Modèle d’options dans ASP.NET Core.
Cette rubrique fournit des informations sur l’injection de dépendances dans ASP.NET Core. La documentation principale sur l’utilisation de l’injection de dépendances est incluse dans Injection de dépendances dans .NET.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Vue d’ensemble de l’injection de dépendances
Une dépendance est un objet dont dépend un autre objet. Examinez la classe MyDependency
suivante avec une méthode WriteMessage
dont dépendent d’autres classes :
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Une classe peut créer une instance de la classe MyDependency
pour utiliser sa méthode WriteMessage
. Dans l’exemple suivant, la classe MyDependency
est une dépendance de la classe IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
La classe est créee et dépend directement de la classe MyDependency
. Les dépendances de code, comme dans l’exemple précédent, posent problème et doivent être évitées pour les raisons suivantes :
- Pour remplacer
MyDependency
par une autre implémentation, la classeIndexModel
doit être modifiée. - Si
MyDependency
possède des dépendances, elles doivent être configurées par la classeIndexModel
. Dans un grand projet comportant plusieurs classes dépendant deMyDependency
, le code de configuration est disséminé dans l’application. - Cette implémentation complique le test unitaire.
L’injection de dépendances résout ces problèmes via :
- 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. ASP.NET Core fournit un conteneur de service intégré, IServiceProvider. Les services sont généralement inscrits dans le fichier
Program.cs
de l’application. - 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.
Dans l’exemple d’application, l’interface IMyDependency
définit la méthode WriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Cette interface est implémentée par un type concret, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L’exemple d’application inscrit le service IMyDependency
avec le type MyDependency
concret. La méthode AddScoped inscrit le service avec une durée de vie délimitée, la durée de vie d’une requête unique. Les durées de vie du service sont décrites plus loin dans cette rubrique.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
Dans l’exemple d’application, le service IMyDependency
est demandé et utilisé pour appeler la méthode WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
En utilisant le modèle d’injection de dépendances, le contrôleur ou la page Razor :
- N’utilise pas le type
MyDependency
concret, uniquement l’interfaceIMyDependency
qu’il implémente. Cela facilite la modification de l’implémentation sans modifier le contrôleur ou la page Razor. - Ne crée pas d’instance de
MyDependency
; elle est créée par le conteneur d’injection de dépendances.
L’implémentation de l’interface IMyDependency
peut être améliorée à l’aide de l’API de journalisation intégrée :
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Le fichier Program.cs
mis à jour inscrit la nouvelle implémentation IMyDependency
:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
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 qui doivent être résolues est généralement appelé arborescence des dépendances, graphique de dépendance 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).
Dans 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
IMyDependency
. - 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 implémentations IMyDependency
indiquées dans les exemples précédents ont été écrites pour démontrer l’injection de dépendances de base, et non pour implémenter 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 ne nécessite l’inscription d’aucun service :
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
En utilisant le code précédent, il n’est pas nécessaire de mettre à jour Program.cs
, car la journalisation est fournie par l’infrastructure.
Inscrire des groupes de services avec des méthodes d’extension
L’infrastructure ASP.NET Core utilise 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 AddControllers inscrit les services requis pour les contrôleurs MVC.
Le code suivant est généré par le modèle des pages Razor à l’aide de comptes d’utilisateur individuels et montre comment ajouter des services supplémentaires au conteneur à l’aide des méthodes d’extension AddDbContext et AddDefaultIdentity :
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Examinons les éléments suivants permettant d’inscrire les services et de configurer les options :
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
var app = builder.Build();
Les groupes d’inscriptions associés peuvent être déplacés vers une méthode d’extension pour inscrire les services. Les services de configuration sont par exemple ajoutés à la classe suivante :
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Les services qui restent sont inscrits dans une classe similaire. Le code suivant utilise les nouvelles méthodes d’extension pour inscrire les services :
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Remarque : chaque méthode d’extension services.Add{GROUP_NAME}
ajoute des services et les configure éventuellement. Par exemple, AddControllersWithViews ajoute les services nécessaires à MVC avec vues et AddRazorPages ajoute les services nécessaires aux pages Razor.
Durées de service
Consultez Durées de service dans Injection de dépendances dans .NET
Pour utiliser des services délimités dans un intergiciel, utilisez l’une des approches suivantes :
- Injectez le service dans la méthode
Invoke
ouInvokeAsync
de l’intergiciel. L’utilisation de l’injection de constructeur lève une exception de runtime, car elle force le service délimité à se comporter comme un singleton. L’exemple utilisé dans la section Options de durée de vie et d’inscription illustre l’approcheInvokeAsync
. - Utilisez un intergiciel basé sur une fabrique. L’intergiciel inscrit à l’aide de cette approche est activé par demande client (connexion), ce qui permet d’injecter des services délimités dans le constructeur de l’intergiciel.
Pour plus d’informations, consultez Écrire un intergiciel ASP.NET Core personnalisé.
Méthodes d’inscription du service
Consultez Méthodes d’inscription de service dans Injection de dépendances dans .NET
Il est courant d’utiliser plusieurs implémentations lors d’une simulation de types à des fins de test.
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. C’est pourquoi plusieurs implémentations d’un service ne peuvent pas être inscrites à l’aide des méthodes qui n’acceptent pas de type de service explicite. Ces méthodes peuvent inscrire plusieurs instances d’un service, mais elles auront toutes le même type d’implémentation.
L’une des méthodes d’inscription de service ci-dessus peut être utilisée pour inscrire plusieurs instances de service du même type de service. Dans l’exemple suivant, AddSingleton
est appelé deux fois avec IMyDependency
comme type de service. Le deuxième appel à AddSingleton
remplace le précédent lorsqu’il est résolu en tant que IMyDependency
et s’ajoute au précédent lorsque plusieurs services sont résolus via IEnumerable<IMyDependency>
. Les services apparaissent dans l’ordre dans lequel ils ont été inscrits lorsqu’ils sont résolus via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Comportement d’injection de constructeurs
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Contextes Entity Framework
Par défaut, les contextes Entity Framework sont ajoutés au conteneur de service en utilisant la durée de vie limitée, car la portée des opérations de base de données d’application web est normalement limitée à la demande du client. Pour utiliser une durée de vie différente, spécifiez la durée de vie à l’aide d’une surcharge AddDbContext. Les services d’une durée de vie donnée ne doivent pas utiliser un contexte de base de données dont la durée de vie est plus courte que celle du service.
Options de durée de vie et d’inscription
Pour illustrer la différence entre les options de durée de vie et d’inscription de service, considérez les interfaces suivantes qui représentent une tâche en tant qu’opération avec un identificateur, OperationId
. Selon la façon dont la durée de vie d’un service d’opérations est configurée pour les interfaces suivantes, le conteneur fournit la même instance ou une instance différente du service lorsqu’une classe le demande :
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
La classe Operation
suivante implémente toutes les interfaces précédentes. Le constructeur Operation
génère un GUID et stocke les 4 derniers caractères dans la propriété OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Le code suivant crée plusieurs inscriptions de la classe Operation
en fonction des durées de vie nommées :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
L’exemple d’application illustre les durées de vie des objets au sein et entre des demandes. IndexModel
et l’intergiciel demandent chaque type de IOperation
et consignent OperationId
pour chacun :
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
Comme IndexModel
, l’intergiciel résout les mêmes services :
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Les services délimités et temporaires doivent être résolus dans la méthode InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
La sortie de l’enregistreur d’événements montre :
- Les objets Transient sont toujours différents. La valeur
OperationId
temporaire est différente dansIndexModel
et dans l’intergiciel. - Les objets délimités sont les mêmes pour une demande donnée, mais diffèrent pour chaque nouvelle demande.
- Les objets singleton sont identiques pour chaque demande.
Pour réduire la sortie de journalisation, définissez « Logging:LogLevel:Microsoft:Error » dans le fichier appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Résoudre un service au démarrage de l’application
Le code suivant montre comment résoudre un service délimité pendant une durée limitée quand l’application démarre :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
app.MapGet("/", () => "Hello World!");
app.Run();
Validation de l’étendue
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Pour plus d’informations, consultez Validation de l’étendue.
Services de requête
Les services et leurs dépendances au sein d’une demande ASP.NET Core sont exposés via HttpContext.RequestServices.
L’infrastructure crée une étendue par demande et RequestServices
expose le fournisseur de services délimité. Tous les services délimités sont valides tant que la demande est active.
Remarque
Préférez demander des dépendances en tant que paramètres de constructeur plutôt que résoudre les services à partir de RequestServices
. La demande de dépendances en tant que paramètres de constructeur génère des classes plus faciles à tester.
Conception de services pour l’injection de dépendances
Lors de la conception de services pour l’injection de dépendances :
- Évitez les classes et les membres statiques avec état. Évitez de créer un état global en concevant des applications pour utiliser des services singleton à la place.
- Éviter une instanciation directe de classes dépendantes au sein de services. L’instanciation directe associe le code à une implémentation particulière.
- Limitez la taille des services, faites en sorte qu’elles soient bien factorisées et facilement testées.
Si une classe possède de nombreuses dépendances injectées, il peut s’agir d’un signe que la classe a trop de responsabilités et viole le principe de responsabilité unique (SRP). Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans de nouvelles classes. N’oubliez pas que les classes du modèle de page Razor Pages et les classes du contrôleur MVC doivent se concentrer sur les problèmes d’interface utilisateur.
Suppression des services
Le conteneur appelle Dispose pour les types IDisposable qu’il crée. Les services résolus à partir du conteneur ne doivent jamais être supprimés par le développeur. Si un type ou une fabrique est inscrit en tant que singleton, le conteneur supprime automatiquement le singleton.
Dans l’exemple suivant, les services sont créés par le conteneur de service et supprimés automatiquement : dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
La console de débogage affiche la sortie suivante après chaque actualisation de la page Index :
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Services non créés par le conteneur de services
Prenez le code suivant :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
Dans le code précédent :
- Les instances de service ne sont pas créées par le conteneur de service.
- L’infrastructure ne supprime pas automatiquement les services.
- Le développeur est responsable de la suppression des services.
Conseils sur l’interface IDisposable pour les instances temporaires et partagées
Consultez Conseils sur l’interface IDisposable pour les instances temporaires et partagées dans Injection de dépendances dans .NET
Remplacement de conteneur de services par défaut
Consultez Remplacement de conteneur de services par défaut dans Injection de dépendances dans .NET
Recommandations
Consultez Recommandations dans Injection de dépendances dans .NET
Évitez d’utiliser le modèle de localisateur de service. Par exemple, n’appelez pas GetService pour obtenir une instance de service si vous pouvez utiliser l’injection de dépendance à la place :
Incorrect :
Correct :
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Une autre variante du localisateur de service à éviter est l’injection d’une fabrique qui résout les dépendances au moment de l’exécution. Ces deux pratiques combinent des stratégies Inversion de contrôle.
Évitez l’accès statique à
HttpContext
(par exemple, IHttpContextAccessor.HttpContext).
L’injection de dépendance constitue une alternative aux modèles d’accès aux objets statiques/globaux. Il est possible que vous ne bénéficiez pas des avantages de l’injection de dépendances si vous la combinez avec l’accès aux objets statiques.
Modèles recommandés pour la multilocation dans l’injection de dépendances
Orchard Core est une infrastructure d’application permettant de créer des applications modulaires multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation Orchard Core.
Consultez les exemples Orchard Core pour obtenir des exemples de création d’applications modulaires et multilocataires utilisant simplement l’infrastructure Orchard Core sans aucune de ses fonctionnalités spécifiques à CMS.
Services fournis par le framework
Program.cs
inscrit les services utilisés par l’application, notamment les fonctionnalités de plateforme comme Entity Framework Core et ASP.NET Core MVC. Au départ, IServiceCollection
fournie à Program.cs
a les services définis par l’infrastructure en fonction de la manière dont l’hôte a été configuré. Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure inscrit plus de 250 services.
Le tableau suivant répertorie un petit exemple de ces services inscrits dans l’infrastructure :
Type de service | Durée de vie |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Temporaire |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Temporaire |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Temporaire |
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.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ressources supplémentaires
- Injection de dépendances dans les vues dans ASP.NET Core
- Injection de dépendances dans les contrôleurs dans ASP.NET Core
- Injection de dépendances dans les gestionnaires d’exigences dans ASP.NET Core
- Injection de dépendances ASP.NET Core Blazor
- NDC Conference Patterns for DI app development
- Démarrage d’une application dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Quatre façons de supprimer les IDisposables dans ASP.NET Core
- Écrire un code clair dans ASP.NET Core avec l’injection de dépendance (MSDN)
- Principe des dépendances explicites
- Conteneurs d’inversion de contrôle et modèle d’injection de dépendances (Martin Fowler)
- Comment inscrire un service avec plusieurs interfaces dans l’injection de dépendance ASP.NET Core
Par Kirk Larkin, Steve Smith, Scott Addie et Brandon Dahler
ASP.NET Core 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.
Pour plus d’informations sur l’injection de dépendances dans les contrôleurs MVC, consultez Injection de dépendances dans les contrôleurs dans ASP.NET Core.
Pour obtenir des informations sur l’utilisation de l’injection de dépendances dans des applications autres que des applications web, consultez Injection de dépendances dans .NET.
Pour plus d’informations sur l’injection de dépendances d’options, consultez Modèle d’options dans ASP.NET Core.
Cette rubrique fournit des informations sur l’injection de dépendances dans ASP.NET Core. La documentation principale sur l’utilisation de l’injection de dépendances est incluse dans Injection de dépendances dans .NET.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Vue d’ensemble de l’injection de dépendances
Une dépendance est un objet dont dépend un autre objet. Examinez la classe MyDependency
suivante avec une méthode WriteMessage
dont dépendent d’autres classes :
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Une classe peut créer une instance de la classe MyDependency
pour utiliser sa méthode WriteMessage
. Dans l’exemple suivant, la classe MyDependency
est une dépendance de la classe IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet created this message.");
}
}
La classe est créee et dépend directement de la classe MyDependency
. Les dépendances de code, comme dans l’exemple précédent, posent problème et doivent être évitées pour les raisons suivantes :
- Pour remplacer
MyDependency
par une autre implémentation, la classeIndexModel
doit être modifiée. - Si
MyDependency
possède des dépendances, elles doivent être configurées par la classeIndexModel
. Dans un grand projet comportant plusieurs classes dépendant deMyDependency
, le code de configuration est disséminé dans l’application. - Cette implémentation complique le test unitaire. L’application doit utiliser une classe
MyDependency
fictive ou stub, ce qui est impossible avec cette approche.
L’injection de dépendances résout ces problèmes via :
- 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. ASP.NET Core fournit un conteneur de service intégré, IServiceProvider. Les services sont généralement inscrits dans la méthode
Startup.ConfigureServices
de l’application. - 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.
Dans l’exemple d’application, l’interface IMyDependency
définit la méthode WriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Cette interface est implémentée par un type concret, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L’exemple d’application inscrit le service IMyDependency
avec le type MyDependency
concret. La méthode AddScoped inscrit le service avec une durée de vie délimitée, la durée de vie d’une requête unique. Les durées de vie du service sont décrites plus loin dans cette rubrique.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddRazorPages();
}
Dans l’exemple d’application, le service IMyDependency
est demandé et utilisé pour appeler la méthode WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
En utilisant le modèle d’injection de dépendances, le contrôleur :
- N’utilise pas le type
MyDependency
concret, uniquement l’interfaceIMyDependency
qu’il implémente. Cela facilite la modification de l’implémentation qu’utilise le contrôleur sans modifier le contrôleur. - Ne crée pas d’instance de
MyDependency
; elle est créée par le conteneur d’injection de dépendances.
L’implémentation de l’interface IMyDependency
peut être améliorée à l’aide de l’API de journalisation intégrée :
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
La méthode ConfigureServices
mise à jour inscrit la nouvelle implémentation IMyDependency
:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency2>();
services.AddRazorPages();
}
MyDependency2
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 qui doivent être résolues est généralement appelé arborescence des dépendances, graphique de dépendance 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).
Dans 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
IMyDependency
. - 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 implémentations IMyDependency
indiquées dans les exemples précédents ont été écrites pour démontrer l’injection de dépendances de base, et non pour implémenter 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 ne nécessite l’inscription d’aucun service dans ConfigureServices
:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; }
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
En utilisant le code précédent, il n’est pas nécessaire de mettre à jour ConfigureServices
, car la journalisation est fournie par l’infrastructure.
Services injectés au démarrage
Les services peuvent être injectés dans le constructeur Startup
et la méthode Startup.Configure
.
Seuls les services suivants peuvent être injectés dans le constructeur Startup
lorsque vous utilisez l’hôte générique (IHostBuilder) :
Tout service inscrit auprès du conteneur d’injection de dépendances peut être injecté dans la méthode Startup.Configure
:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
...
}
Pour plus d’informations, consultez Démarrage d’une application dans ASP.NET Core et Configuration de l’accès au démarrage.
Inscrire des groupes de services avec des méthodes d’extension
L’infrastructure ASP.NET Core utilise 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 AddControllers inscrit les services requis pour les contrôleurs MVC.
Le code suivant est généré par le modèle des pages Razor à l’aide de comptes d’utilisateur individuels et montre comment ajouter des services supplémentaires au conteneur à l’aide des méthodes d’extension AddDbContext et AddDefaultIdentity :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
}
Examinons la méthode ConfigureServices
suivante qui inscrit les services et configure les options :
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(
Configuration.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
Configuration.GetSection(ColorOptions.Color));
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
services.AddRazorPages();
}
Les groupes d’inscriptions associés peuvent être déplacés vers une méthode d’extension pour inscrire les services. Les services de configuration sont par exemple ajoutés à la classe suivante :
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
Les services qui restent sont inscrits dans une classe similaire. La méthode ConfigureServices
suivante utilise les nouvelles méthodes d’extension pour inscrire les services :
public void ConfigureServices(IServiceCollection services)
{
services.AddConfig(Configuration)
.AddMyDependencyGroup();
services.AddRazorPages();
}
Remarque : chaque méthode d’extension services.Add{GROUP_NAME}
ajoute des services et les configure éventuellement. Par exemple, AddControllersWithViews ajoute les services nécessaires à MVC avec vues et AddRazorPages ajoute les services nécessaires aux pages Razor. Nous recommandons que les applications suivent la convention de nommage associée à la création des méthodes d’extension dans l’espace de noms Microsoft.Extensions.DependencyInjection. Création de méthodes d’extension dans l’espace de noms Microsoft.Extensions.DependencyInjection
:
- Encapsule des groupes d’inscriptions de service.
- Fournit un accès IntelliSense pratique au service.
Durées de service
Consultez Durées de service dans Injection de dépendances dans .NET
Pour utiliser des services délimités dans un intergiciel, utilisez l’une des approches suivantes :
- Injectez le service dans la méthode
Invoke
ouInvokeAsync
de l’intergiciel. L’utilisation de l’injection de constructeur lève une exception de runtime, car elle force le service délimité à se comporter comme un singleton. L’exemple utilisé dans la section Options de durée de vie et d’inscription illustre l’approcheInvokeAsync
. - Utilisez un intergiciel basé sur une fabrique. L’intergiciel inscrit à l’aide de cette approche est activé par demande client (connexion), ce qui permet d’injecter des services délimités dans la méthode
InvokeAsync
de l’intergiciel.
Pour plus d’informations, consultez Écrire un intergiciel ASP.NET Core personnalisé.
Méthodes d’inscription du service
Consultez Méthodes d’inscription de service dans Injection de dépendances dans .NET
Il est courant d’utiliser plusieurs implémentations lors d’une simulation de types à des fins de test.
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. C’est pourquoi plusieurs implémentations d’un service ne peuvent pas être inscrites à l’aide des méthodes qui n’acceptent pas de type de service explicite. Ces méthodes peuvent inscrire plusieurs instances d’un service, mais elles auront toutes le même type d’implémentation.
L’une des méthodes d’inscription de service ci-dessus peut être utilisée pour inscrire plusieurs instances de service du même type de service. Dans l’exemple suivant, AddSingleton
est appelé deux fois avec IMyDependency
comme type de service. Le deuxième appel à AddSingleton
remplace le précédent lorsqu’il est résolu en tant que IMyDependency
et s’ajoute au précédent lorsque plusieurs services sont résolus via IEnumerable<IMyDependency>
. Les services apparaissent dans l’ordre dans lequel ils ont été inscrits lorsqu’ils sont résolus via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Comportement d’injection de constructeurs
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Contextes Entity Framework
Par défaut, les contextes Entity Framework sont ajoutés au conteneur de service en utilisant la durée de vie limitée, car la portée des opérations de base de données d’application web est normalement limitée à la demande du client. Pour utiliser une durée de vie différente, spécifiez la durée de vie à l’aide d’une surcharge AddDbContext. Les services d’une durée de vie donnée ne doivent pas utiliser un contexte de base de données dont la durée de vie est plus courte que celle du service.
Options de durée de vie et d’inscription
Pour illustrer la différence entre les options de durée de vie et d’inscription de service, considérez les interfaces suivantes qui représentent une tâche en tant qu’opération avec un identificateur, OperationId
. Selon la façon dont la durée de vie d’un service d’opérations est configurée pour les interfaces suivantes, le conteneur fournit la même instance ou une instance différente du service lorsqu’une classe le demande :
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
La classe Operation
suivante implémente toutes les interfaces précédentes. Le constructeur Operation
génère un GUID et stocke les 4 derniers caractères dans la propriété OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
La méthode Startup.ConfigureServices
crée plusieurs inscriptions de la classe Operation
en fonction des durées de vie nommées :
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddRazorPages();
}
L’exemple d’application illustre les durées de vie des objets au sein et entre des demandes. IndexModel
et l’intergiciel demandent chaque type de IOperation
et consignent OperationId
pour chacun :
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
Comme IndexModel
, l’intergiciel résout les mêmes services :
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationTransient transientOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Les services délimités doivent être résolus dans la méthode InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
La sortie de l’enregistreur d’événements montre :
- Les objets Transient sont toujours différents. La valeur
OperationId
temporaire est différente dansIndexModel
et dans l’intergiciel. - Les objets délimités sont les mêmes pour une demande donnée, mais diffèrent pour chaque nouvelle demande.
- Les objets singleton sont identiques pour chaque demande.
Pour réduire la sortie de journalisation, définissez « Logging:LogLevel:Microsoft:Error » dans le fichier appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Appeler des services à partir de Main
Créez un IServiceScope avec IServiceScopeFactory.CreateScope pour résoudre un service délimité dans l’étendue de l’application. Cette approche est pratique pour accéder à un service Scoped au démarrage pour exécuter des tâches d’initialisation.
L’exemple suivant montre comment accéder au service IMyDependency
délimité et appeler sa méthode WriteMessage
dans Program.Main
:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Validation de l’étendue
Consultez Comportement d’injection de constructeurs dans Injection de dépendances dans .NET
Pour plus d’informations, consultez Validation de l’étendue.
Services de requête
Les services et leurs dépendances au sein d’une demande ASP.NET Core sont exposés via HttpContext.RequestServices.
L’infrastructure crée une étendue par demande et RequestServices
expose le fournisseur de services délimité. Tous les services délimités sont valides tant que la demande est active.
Remarque
Préférez demander des dépendances en tant que paramètres de constructeur plutôt que résoudre les services à partir de RequestServices
. La demande de dépendances en tant que paramètres de constructeur génère des classes plus faciles à tester.
Conception de services pour l’injection de dépendances
Lors de la conception de services pour l’injection de dépendances :
- Évitez les classes et les membres statiques avec état. Évitez de créer un état global en concevant des applications pour utiliser des services singleton à la place.
- Éviter une instanciation directe de classes dépendantes au sein de services. L’instanciation directe associe le code à une implémentation particulière.
- Limitez la taille des services, faites en sorte qu’elles soient bien factorisées et facilement testées.
Si une classe possède de nombreuses dépendances injectées, il peut s’agir d’un signe que la classe a trop de responsabilités et viole le principe de responsabilité unique (SRP). Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans de nouvelles classes. N’oubliez pas que les classes du modèle de page Razor Pages et les classes du contrôleur MVC doivent se concentrer sur les problèmes d’interface utilisateur.
Suppression des services
Le conteneur appelle Dispose pour les types IDisposable qu’il crée. Les services résolus à partir du conteneur ne doivent jamais être supprimés par le développeur. Si un type ou une fabrique est inscrit en tant que singleton, le conteneur supprime automatiquement le singleton.
Dans l’exemple suivant, les services sont créés par le conteneur de service et supprimés automatiquement :
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
var myKey = Configuration["MyKey"];
services.AddSingleton<IService3>(sp => new Service3(myKey));
services.AddRazorPages();
}
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
La console de débogage affiche la sortie suivante après chaque actualisation de la page Index :
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose
Services non créés par le conteneur de services
Prenez le code suivant :
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new Service1());
services.AddSingleton(new Service2());
services.AddRazorPages();
}
Dans le code précédent :
- Les instances de service ne sont pas créées par le conteneur de service.
- L’infrastructure ne supprime pas automatiquement les services.
- Le développeur est responsable de la suppression des services.
Conseils sur l’interface IDisposable pour les instances temporaires et partagées
Consultez Conseils sur l’interface IDisposable pour les instances temporaires et partagées dans Injection de dépendances dans .NET
Remplacement de conteneur de services par défaut
Consultez Remplacement de conteneur de services par défaut dans Injection de dépendances dans .NET
Recommandations
Consultez Recommandations dans Injection de dépendances dans .NET
Évitez d’utiliser le modèle de localisateur de service. Par exemple, n’appelez pas GetService pour obtenir une instance de service si vous pouvez utiliser l’injection de dépendance à la place :
Incorrect :
Correct :
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Une autre variante du localisateur de service à éviter est l’injection d’une fabrique qui résout les dépendances au moment de l’exécution. Ces deux pratiques combinent des stratégies Inversion de contrôle.
Évitez l’accès statique à
HttpContext
(par exemple, IHttpContextAccessor.HttpContext).
Évitez les appels à BuildServiceProvider dans
ConfigureServices
. L’appel deBuildServiceProvider
se produit généralement lorsque le développeur souhaite résoudre un service dansConfigureServices
. Par exemple, considérez le cas oùLoginPath
est chargé à partir de la configuration. Évitez l’approche suivante :Dans l’image précédente, la sélection de la ligne ondulée verte sous
services.BuildServiceProvider
affiche l’avertissement ASP0000 suivant :ASP0000 L’appel de « BuildServiceProvider » à partir du code d’application entraîne la création d’une copie supplémentaire des services singleton. Envisagez des alternatives telles que l’injection de dépendances de services en tant que paramètres pour « Configurer ».
L’appel de
BuildServiceProvider
crée un deuxième conteneur, qui peut créer des singletons endommagés et provoquer des références à des graphiques d’objets sur plusieurs conteneurs.Une bonne façon d’obtenir
LoginPath
consiste à utiliser la prise en charge intégrée du modèle d’options pour l’injection de dépendances :public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); services.AddOptions<CookieAuthenticationOptions>( CookieAuthenticationDefaults.AuthenticationScheme) .Configure<IMyService>((options, myService) => { options.LoginPath = myService.GetLoginPath(); }); services.AddRazorPages(); }
Les services temporaires supprimables sont capturés par le conteneur à des fins d’élimination. Cela peut se transformer en fuite de mémoire si le service est résolu à partir du conteneur de niveau supérieur.
Activez la validation de l’étendue pour vous assurer que l’application n’a pas de singletons qui capturent des services limités. Pour plus d’informations, consultez Validation de l’étendue.
Comme pour toutes les recommandations, vous pouvez vous trouver dans des situations où il est nécessaire d’ignorer une recommandation. Les exceptions sont rares et sont principalement des cas particuliers dans l’infrastructure elle-même.
L’injection de dépendance constitue une alternative aux modèles d’accès aux objets statiques/globaux. Il est possible que vous ne bénéficiez pas des avantages de l’injection de dépendances si vous la combinez avec l’accès aux objets statiques.
Modèles recommandés pour la multilocation dans l’injection de dépendances
Orchard Core est une infrastructure d’application permettant de créer des applications modulaires multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation Orchard Core.
Consultez les exemples Orchard Core pour obtenir des exemples de création d’applications modulaires et multilocataires utilisant simplement l’infrastructure Orchard Core sans aucune de ses fonctionnalités spécifiques à CMS.
Services fournis par le framework
La méthode Startup.ConfigureServices
inscrit les services utilisés par l’application, notamment les fonctionnalités de plateforme comme Entity Framework Core et ASP.NET Core MVC. Au départ, IServiceCollection
fournie à ConfigureServices
a les services définis par l’infrastructure en fonction de la manière dont l’hôte a été configuré. Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure inscrit plus de 250 services.
Le tableau suivant répertorie un petit exemple de ces services inscrits dans l’infrastructure :
Type de service | Durée de vie |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Temporaire |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Temporaire |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Temporaire |
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.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ressources supplémentaires
- Injection de dépendances dans les vues dans ASP.NET Core
- Injection de dépendances dans les contrôleurs dans ASP.NET Core
- Injection de dépendances dans les gestionnaires d’exigences dans ASP.NET Core
- Injection de dépendances ASP.NET Core Blazor
- NDC Conference Patterns for DI app development
- Démarrage d’une application dans ASP.NET Core
- Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
- Quatre façons de supprimer les IDisposables dans ASP.NET Core
- Écrire un code clair dans ASP.NET Core avec l’injection de dépendance (MSDN)
- Principe des dépendances explicites
- Conteneurs d’inversion de contrôle et modèle d’injection de dépendances (Martin Fowler)
- Comment inscrire un service avec plusieurs interfaces dans l’injection de dépendance ASP.NET Core