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, la IndexModel classe doit être modifiée.
  • Si MyDependency a des dépendances, elles doivent également être configurées par la IndexModel classe . Dans un grand projet comportant plusieurs classes dépendant de MyDependency, 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 MyDependencyconcret . 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 MyDependencyconcret , uniquement l’interface IMyDependency 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 :

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 et IndexModel 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 :

    Code 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.

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.csinscrit 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

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, la IndexModel classe doit être modifiée.
  • Si MyDependency a des dépendances, elles doivent également être configurées par la IndexModel classe . Dans un grand projet comportant plusieurs classes dépendant de MyDependency, 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 MyDependencyconcret . 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 MyDependencyconcret , uniquement l’interface IMyDependency 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 :

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 et IndexModel 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 :

    Code 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 BuildServiceProvider se produit généralement lorsque le développeur souhaite résoudre un service dans ConfigureServices. Par exemple, considérez le cas où est chargé à partir de la LoginPath configuration. Évitez l’approche suivante :

    code incorrect appelant BuildServiceProvider

    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.

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