Injection de dépendances dans ASP.NET Core
Par Kirk Larkin, Steve Smith et Brandon Dahler
ASP.NET Core prend en charge le modèle de conception logicielle d’injection de dépendances, technique qui permet d’obtenir une inversion de contrôle 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 des contrôleurs dans ASP.NET Core.
Pour plus d’informations sur l’utilisation de l’injection de dépendances dans des applications autres que les 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 se trouve 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 suivante MyDependency
avec une WriteMessage
méthode 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 MyDependency
classe pour utiliser sa WriteMessage
méthode. Dans l’exemple suivant, la MyDependency
classe est une dépendance de la IndexModel
classe :
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
La classe crée et dépend directement de la MyDependency
classe . Les dépendances de code, comme dans l’exemple précédent, sont problématiques et doivent être évitées pour les raisons suivantes :
- Pour remplacer par
MyDependency
une autre implémentation, laIndexModel
classe doit être modifiée. - Si
MyDependency
a des dépendances, elles doivent également être configurées par laIndexModel
classe . 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 de
Program.cs
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 WriteMessage
méthode :
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 IMyDependency
service avec le type MyDependency
concret . La AddScoped méthode 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 IMyDependency
service est demandé et utilisé pour appeler la WriteMessage
méthode :
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
À l’aide du modèle d’AUTHENTIFICATION, le contrôleur ou Razor la page :
- 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 Razor la page. - Ne crée pas d’instance de
MyDependency
, elle est créée par le conteneur DI.
L’implémentation de l’interface peut être améliorée à l’aide IMyDependency
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 mise à jour Program.cs
inscrit la nouvelle IMyDependency
implémentation :
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 en ILogger<TCategoryName>
tirant parti des types ouverts (génériques), éliminant ainsi la nécessité d’inscrire chaque type construit (générique).
Dans la terminologie relative à l’injection de dépendances, un service :
- Est généralement un objet qui fournit un service à d’autres objets, tels que le
IMyDependency
service. - 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 IMyDependency
implémentations indiquées dans les exemples précédents ont été écrites pour illustrer l’AUTHENTIFICATION 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 pas d’inscription de services :
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 Add{GROUP_NAME}
méthode d’extension 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 Pages à l’aide Razor de comptes d’utilisateur individuels et montre comment ajouter des services supplémentaires au conteneur à l’aide des méthodes AddDbContext d’extension 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 vie de service dans l’injection de dépendances dans .NET
Pour utiliser des services délimités dans les intergiciels, utilisez l’une des approches suivantes :
- Injectez le service dans la méthode ou
InvokeAsync
l’intergicielInvoke
. L’utilisation de l’injection de constructeur lève une exception d’exécution, car elle force le service délimité à se comporter comme un singleton. L’exemple de la section Options de durée de vie et d’inscription illustre l’approcheInvokeAsync
. - Utilisez un intergiciel basé sur les fabriques. Les intergiciels inscrits à l’aide de cette approche sont activés par demande client (connexion), ce qui permet d’injecter des services délimités dans le constructeur du middleware.
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 de la simulation de types à des fins de test.
L’inscription d’un service avec uniquement un type d’implémentation équivaut à 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 ne prennent 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 de l’injection du constructeur dans Injection de dépendances dans .NET
Contextes Entity Framework
Par défaut, les contextes Entity Framework sont ajoutés au conteneur de service à l’aide de la durée de vie délimitée , car les opérations de base de données d’application web sont normalement limitées à la demande cliente. Pour utiliser une autre durée de vie, spécifiez la durée de vie à l’aide d’une AddDbContext surcharge. 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 inférieure à celle du service.
Options de durée de vie et d’inscription
Pour illustrer la différence entre les durées de vie de service et leurs options d’inscription, 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 du service d’une opération est configurée pour les interfaces suivantes, le conteneur fournit les instances identiques ou différentes 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 suivante Operation
implémente toutes les interfaces précédentes. Le Operation
constructeur génère un GUID et stocke les 4 derniers caractères dans la OperationId
propriété :
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 Operation
classe 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 à la fois dans et entre les requêtes. Le IndexModel
et l’intergiciel demandent chaque type de IOperation
type et journalisent le OperationId
pour chaque :
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);
}
}
À l’instar de 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 InvokeAsync
méthode :
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 affiche :
- Les objets Transient sont toujours différents. La valeur temporaire
OperationId
est différente dans etIndexModel
dans l’intergiciel. - Les objets délimités sont identiques pour une demande donnée, mais diffèrent pour chaque nouvelle requête.
- Les objets Singleton sont les mêmes pour chaque requête.
Pour réduire la sortie de journalisation, définissez « Logging:LogLevel:Microsoft:Error » dans le appsettings.Development.json
fichier :
{
"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 au démarrage de l’application :
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 constructeur 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 de 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.
Notes
Préférez demander des dépendances en tant que paramètres de constructeur plutôt que de 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.
- Rendre les services petits, bien pris en compte et facilement testés.
Si une classe a un grand nombre de dépendances injectées, cela peut être un signe que la classe a trop de responsabilités et qu’elle enfreint le principe de responsabilité unique (SRP). Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans de nouvelles classes. Gardez à l’esprit que Razor les classes de modèle de page Pages et les classes de 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\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
Service1.Dispose
Services non créés par le conteneur de service
Considérez 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 dispose pas automatiquement des services.
- Le développeur est responsable de la suppression des services.
Conseils IDisposable pour les instances temporaires et partagées
Consultez Guide 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 du conteneur de service par défaut dans Injection de dépendances dans .NET
Recommandations
Consultez Recommandations dans l’injection de dépendances dans .NET
Évitez d’utiliser le modèle de localisation 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 l’architecture mutualisée dans l’intégration d’identité
Orchard Core est une infrastructure d’application permettant de créer des applications modulaires et multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation principale d’Orchard.
Consultez les exemples Orchard Core pour obtenir des exemples de création d’applications modulaires et multilocataires à l’aide de l’infrastructure Orchard Core sans aucune de ses fonctionnalités spécifiques à CMS.
Services fournis par le framework
Program.cs
inscrit les services que l’application utilise, y compris les fonctionnalités de plateforme, telles qu’Entity Framework Core et ASP.NET Core MVC. Initialement, le fourni à Program.cs
a des services définis par l’infrastructure en fonction de la façon dont l’hôte a été configuré.IServiceCollection
Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure enregistre plus de 250 services.
Le tableau suivant répertorie un petit échantillon 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 de conditions requises dans ASP.NET Core
- injection de dépendances Blazor ASP.NET Core
- Modèles de conférence NDC pour le développement d’applications DI
- 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 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)
- Guide pratique pour inscrire un service comportant 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 logicielle d’injection de dépendances, technique qui permet d’obtenir une inversion de contrôle 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 plus d’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 contenue 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 suivante MyDependency
avec une WriteMessage
méthode 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 MyDependency
classe pour utiliser sa WriteMessage
méthode. Dans l’exemple suivant, la MyDependency
classe est une dépendance de la IndexModel
classe :
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet created this message.");
}
}
La classe crée et dépend directement de la MyDependency
classe . Les dépendances de code, comme dans l’exemple précédent, sont problématiques et doivent être évitées pour les raisons suivantes :
- Pour remplacer par
MyDependency
une autre implémentation, laIndexModel
classe doit être modifiée. - Si
MyDependency
a des dépendances, elles doivent également être configurées par laIndexModel
classe . 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 de
Startup.ConfigureServices
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 WriteMessage
méthode :
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 IMyDependency
service avec le type MyDependency
concret . La AddScoped méthode 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 IMyDependency
service est demandé et utilisé pour appeler la WriteMessage
méthode :
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
À l’aide du modèle DI, 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 que le contrôleur utilise sans modifier le contrôleur. - Ne crée pas d’instance de
MyDependency
, elle est créée par le conteneur DI.
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 mise à jour ConfigureServices
enregistre la nouvelle IMyDependency
implémentation :
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency2>();
services.AddRazorPages();
}
MyDependency2
dépend ILogger<TCategoryName>de , 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 est ILogger<TCategoryName>
résolu en tirant parti des types ouverts (génériques), ce qui élimine la nécessité d’inscrire chaque type (générique) construit.
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
IMyDependency
service. - 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 IMyDependency
implémentations présentées dans les exemples précédents ont été écrites pour illustrer l’intégration 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 pas d’inscription de services 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);
}
}
À l’aide du 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 dans le démarrage
Les services peuvent être injectés dans le Startup
constructeur et la Startup.Configure
méthode .
Seuls les services suivants peuvent être injectés dans le constructeur lors de l’utilisation Startup
de l’hôte générique () :IHostBuilder
Tout service inscrit auprès du conteneur DI peut être injecté dans la Startup.Configure
méthode :
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
...
}
Pour plus d’informations, consultez Démarrage de l’application dans ASP.NET Core et Configuration d’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 inscrire un groupe de services connexes. La convention consiste à utiliser une seule Add{GROUP_NAME}
méthode d’extension 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 Pages à l’aide Razor de comptes d’utilisateur individuels et montre comment ajouter des services supplémentaires au conteneur à l’aide des méthodes AddDbContext d’extension 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 vie des services dans l’injection de dépendances dans .NET
Pour utiliser des services délimités dans les intergiciels, utilisez l’une des approches suivantes :
- Injectez le service dans la méthode ou
InvokeAsync
de l’intergicielInvoke
. 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 dans la section Options de durée de vie et d’inscription illustre l’approcheInvokeAsync
. - Utilisez l’intergiciel basé sur les fabriques. 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 du
InvokeAsync
middleware.
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 de la 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 comme IMyDependency
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 constructeur dans Injection de dépendances dans .NET
Contextes Entity Framework
Par défaut, les contextes Entity Framework sont ajoutés au conteneur de service à l’aide de la durée de vie délimitée , car les opérations de base de données d’application web sont normalement limitées à la demande du client. Pour utiliser une durée de vie différente, spécifiez la durée de vie à l’aide d’une AddDbContext surcharge. 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 inférieure à celle du service.
Options de durée de vie et d’inscription
Pour illustrer la différence entre les durées de vie du service et leurs options d’inscription, 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 du service d’une opération est configurée pour les interfaces suivantes, le conteneur fournit les instances identiques ou différentes du service quand une classe le demande :
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
La classe suivante Operation
implémente toutes les interfaces précédentes. Le Operation
constructeur génère un GUID et stocke les 4 derniers caractères dans la OperationId
propriété :
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
La Startup.ConfigureServices
méthode crée plusieurs inscriptions de la Operation
classe 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 à la fois dans et entre les requêtes. Le IndexModel
et l’intergiciel demandent chaque type de IOperation
type et journalisent le OperationId
pour chaque :
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);
}
}
À l’instar de 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 InvokeAsync
méthode :
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 affiche :
- Les objets Transient sont toujours différents. La valeur temporaire
OperationId
est différente dans etIndexModel
dans l’intergiciel. - Les objets délimités sont identiques pour une demande donnée, mais diffèrent pour chaque nouvelle requête.
- Les objets Singleton sont les mêmes pour chaque requête.
Pour réduire la sortie de journalisation, définissez « Logging:LogLevel:Microsoft:Error » dans le appsettings.Development.json
fichier :
{
"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 délimité IMyDependency
et appeler sa WriteMessage
méthode 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 constructeur 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 de 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.
Notes
Préférez demander des dépendances en tant que paramètres de constructeur plutôt que de 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.
- Rendre les services petits, bien pris en compte et facilement testés.
Si une classe a un grand nombre de dépendances injectées, cela peut être un signe que la classe a trop de responsabilités et qu’elle enfreint le principe de responsabilité unique (SRP). Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans de nouvelles classes. Gardez à l’esprit que Razor les classes de modèle de page Pages et les classes de 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
Service1.Dispose
Services non créés par le conteneur de service
Considérez 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 dispose pas automatiquement des services.
- Le développeur est responsable de la suppression des services.
Conseils IDisposable pour les instances temporaires et partagées
Consultez Guide 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 du conteneur de service par défaut dans Injection de dépendances dans .NET
Recommandations
Consultez Recommandations dans l’injection de dépendances dans .NET
Évitez d’utiliser le modèle de localisation 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’appelBuildServiceProvider
se produit généralement lorsque le développeur souhaite résoudre un service dansConfigureServices
. Par exemple, considérez le cas où est chargé à partir de laLoginPath
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 services de dépendances en tant que paramètres pour « Configurer ».
L’appel
BuildServiceProvider
crée un deuxième conteneur, qui peut créer des singletons déchiré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’utilisation de données :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 jetables sont capturés par le conteneur pour être supprimés. Cela peut se transformer en une fuite de mémoire si elle est résolue à partir du conteneur de niveau supérieur.
Activez la validation d’étendue pour vous assurer que l’application n’a pas de singletons qui capturent les services dé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, la plupart du temps des cas particuliers au sein du framework lui-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 l’architecture mutualisée dans l’intégration d’identité
Orchard Core est une infrastructure d’application permettant de créer des applications modulaires et multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation principale d’Orchard.
Consultez les exemples Orchard Core pour obtenir des exemples de création d’applications modulaires et multilocataires à l’aide de l’infrastructure Orchard Core sans aucune de ses fonctionnalités spécifiques à CMS.
Services fournis par le framework
La Startup.ConfigureServices
méthode inscrit les services que l’application utilise, y compris les fonctionnalités de plateforme, telles qu’Entity Framework Core et ASP.NET Core MVC. Initialement, le fourni à ConfigureServices
a des services définis par l’infrastructure en fonction de la façon dont l’hôte a été configuré.IServiceCollection
Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure enregistre plus de 250 services.
Le tableau suivant répertorie un petit échantillon 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 de conditions requises dans ASP.NET Core
- Blazor injection de dépendances ASP.NET Core
- Modèles de conférence NDC pour le développement d’applications di
- 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)
- Guide pratique pour inscrire un service comportant plusieurs interfaces dans l’injection de dépendance ASP.NET Core