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 spécifiques à l’injection de dépendances dans les contrôleurs MVC, consultez l’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 les applications autres que les applications web, consultez l’injection de dépendances dans .NET.

Pour plus d’informations sur l’injection de dépendances d’options, consultez le modèle 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 l’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 MyDependency par une implémentation différente, la IndexModel classe doit être modifiée.
  • Si MyDependency elle 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 l’application Program.cs .
  • 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 limitée, la durée de vie d’une seule requête. 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");
    }
}

En utilisant le modèle d’injection de dépendances, le contrôleur ou Razor la page :

  • N’utilise pas le type MyDependencyconcret , seule l’interface IMyDependency qu’elle 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 d’injection de dépendances.

L’implémentation de l’interface IMyDependency peut être améliorée à l’aide de l’API de journalisation intégrée :

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

La 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>ce 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 se ILogger<TCategoryName> résout en tirant parti des types ouverts (génériques), éliminant ainsi la nécessité d’inscrire chaque type construit (générique).

Dans la terminologie de 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 présentées dans les exemples précédents ont été écrites pour illustrer l’authentification unique 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);
    }
}

À l’aide du 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 inscrire un groupe de services associés. La convention consiste à utiliser une méthode d’extension unique Add{GROUP_NAME} pour inscrire tous les services requis par une fonctionnalité d’infrastructure. Par exemple, la AddControllers méthode d’extension 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;
        }
    }
}

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 les durées de vie du service dans l’injection de dépendances dans .NET

Pour utiliser des services étendus dans le middleware, 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 les méthodes d’inscription de service dans l’injectionde 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 la même implémentation et le même type 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 tous 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 le type de service. Le deuxième appel pour AddSingleton remplacer le précédent lorsqu’il est résolu comme IMyDependency et l’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 ont été 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

Voir comportement d’injection de constructeur dans l’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 étendues à 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 avec une durée de vie plus courte que la durée de vie 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, tenez compte des 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 mêmes instances ou différentes du service quand elle est demandée par une classe :

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. Les IndexModel intergiciels demandent chaque type de IOperation type et journalisent les OperationId données pour chacune d’elles :

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Comme pour le IndexModelmiddleware, le middleware 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 le IndexModel middleware et dans le middleware.
  • Les objets délimités sont les mêmes pour une demande donnée, mais diffèrent entre chaque nouvelle requête.
  • Les objets Singleton sont identiques 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 lorsque l’application démarre :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validation de l’étendue

Voir le comportement d’injection de constructeur dans l’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 requête ASP.NET Core sont exposés via HttpContext.RequestServices.

L’infrastructure crée une étendue par requête et RequestServices expose le fournisseur de services étendu. Tous les services délimités sont valides tant que la requête est active.

Notes

Préférez demander des dépendances en tant que paramètres de constructeur par rapport à la résolution des 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 avec état, statiques. É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.
  • Faites des services petits, bien pris en compte et facilement testés.

Si une classe a beaucoup de dépendances injectées, il peut s’agir d’un signe que la classe a trop de responsabilités et 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 préoccupations de l’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 services

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 supprime pas automatiquement les services.
  • Le développeur est responsable de la suppression des services.

Conseils IDisposables pour les instances temporaires et partagées

Consultez les instructions IDisposables pour l’instance temporaire et partagée dans l’injection de dépendances dans .NET

Remplacement de conteneur de services par défaut

Voir Remplacement du conteneur de service par défaut dans l’injection de dépendances dans .NET

Recommandations

Voir 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 pour la création d’applications modulaires multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation De base du Verger.

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, consultez les exemples de l’exemple de création d’applications multilocataires et modulaires.

Services fournis par le framework

Program.csenregistre les services que l’application utilise, y compris les fonctionnalités de plateforme, telles que Entity Framework Core et ASP.NET Core MVC. Initialement, le IServiceCollection service fourni à Program.cs des services définis par l’infrastructure en fonction de la façon dont l’hôte a été configuré. Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure inscrit plus de 250 services.

Le tableau suivant répertorie un petit exemple de ces services inscrits dans l’infrastructure :

Type de service Durée de vie
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Temporaire
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Temporaire
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Temporaire
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Temporaire
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Ressources supplémentaires

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 spécifiques à l’injection de dépendances dans les contrôleurs MVC, consultez l’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 les applications autres que les applications web, consultez l’injection de dépendances dans .NET.

Pour plus d’informations sur l’injection de dépendances d’options, consultez le modèle 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 l’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 MyDependency par une implémentation différente, la IndexModel classe doit être modifiée.
  • Si MyDependency elle 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 l’application Startup.ConfigureServices .
  • 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 limitée, la durée de vie d’une seule requête. 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 d’injection de dépendances, le contrôleur :

  • N’utilise pas le type MyDependencyconcret , seule l’interface IMyDependency qu’elle implémente. Cela facilite la modification de l’implémentation utilisée par le contrôleur sans modifier le contrôleur.
  • Ne crée pas d’instance de MyDependency, elle est créée par le conteneur d’injection de dépendances.

L’implémentation de l’interface IMyDependency peut être améliorée à l’aide de l’API de journalisation intégrée :

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

La méthode mise à jour ConfigureServices inscrit la nouvelle IMyDependency implémentation :

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency2>();

    services.AddRazorPages();
}

MyDependency2 dépend de ILogger<TCategoryName>ce 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 se ILogger<TCategoryName> résout en tirant parti des types ouverts (génériques), éliminant ainsi la nécessité d’inscrire chaque type construit (générique).

Dans la terminologie de 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 présentées dans les exemples précédents ont été écrites pour illustrer l’authentification unique 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 :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 au 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 Startup constructeur lors de l’utilisation de l’hôte générique (IHostBuilder) :

Tout service inscrit auprès du conteneur d’injection de dépendances 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’Access 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 associés. La convention consiste à utiliser une méthode d’extension unique Add{GROUP_NAME} pour inscrire tous les services requis par une fonctionnalité d’infrastructure. Par exemple, la AddControllers méthode d’extension 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;
        }
    }
}

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 les durées de vie du service dans l’injection de dépendances dans .NET

Pour utiliser des services étendus dans le middleware, 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 les méthodes d’inscription de service dans l’injectionde 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 la même implémentation et le même type 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 tous 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 le type de service. Le deuxième appel pour AddSingleton remplacer le précédent lorsqu’il est résolu comme IMyDependency et l’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 ont été 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

Voir comportement d’injection de constructeur dans l’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 étendues à 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 avec une durée de vie plus courte que la durée de vie 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, tenez compte des interfaces suivantes qui représentent une tâche en tant qu’opération avec un identificateur. OperationId Selon la durée de vie du service d’une opération configurée pour les interfaces suivantes, le conteneur fournit les mêmes instances ou différentes du service quand elle est demandée par une classe :

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 de 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. Les IndexModel intergiciels demandent chaque type de IOperation type et journalisent les OperationId données pour chacune d’elles :

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Comme pour le IndexModelmiddleware, le middleware 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 le IndexModel middleware et dans le middleware.
  • Les objets délimités sont les mêmes pour une demande donnée, mais diffèrent entre chaque nouvelle requête.
  • Les objets Singleton sont identiques 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 étendu 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

Voir le comportement d’injection de constructeur dans l’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 requête ASP.NET Core sont exposés via HttpContext.RequestServices.

L’infrastructure crée une étendue par requête et RequestServices expose le fournisseur de services étendu. Tous les services délimités sont valides tant que la requête est active.

Notes

Préférez demander des dépendances en tant que paramètres de constructeur par rapport à la résolution des 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 avec état, statiques. É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.
  • Faites des services petits, bien pris en compte et facilement testés.

Si une classe a beaucoup de dépendances injectées, il peut s’agir d’un signe que la classe a trop de responsabilités et 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 préoccupations de l’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 services

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 supprime pas automatiquement les services.
  • Le développeur est responsable de la suppression des services.

Conseils IDisposables pour les instances temporaires et partagées

Consultez les instructions IDisposables pour l’instance temporaire et partagée dans l’injection de dépendances dans .NET

Remplacement de conteneur de services par défaut

Voir Remplacement du conteneur de service par défaut dans l’injection de dépendances dans .NET

Recommandations

Voir 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 dans BuildServiceProviderConfigureServices. 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ù le LoginPath chargement est effectué à partir de la 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 Appelant « BuildServiceProvider » à partir du code de l’application entraîne la création d’une copie supplémentaire des services singleton. Envisagez des alternatives telles que l’injection 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 aux 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 :

    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 l’élimination. Cela peut se transformer en fuite de mémoire si elle est résolue à partir du conteneur de niveau supérieur.

  • Activez la validation de l’étendue pour vous assurer que l’application n’a pas de singletons qui capturent des services 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, principalement des cas spéciaux dans le cadre 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 pour la création d’applications modulaires multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la documentation De base du Verger.

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, consultez les exemples de l’exemple de création d’applications multilocataires et modulaires.

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 que Entity Framework Core et ASP.NET Core MVC. Initialement, le IServiceCollection service fourni à ConfigureServices des services définis par l’infrastructure en fonction de la façon dont l’hôte a été configuré. Pour les applications basées sur les modèles ASP.NET Core, l’infrastructure inscrit plus de 250 services.

Le tableau suivant répertorie un petit exemple de ces services inscrits dans l’infrastructure :

Type de service Durée de vie
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Temporaire
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Temporaire
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Temporaire
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Temporaire
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Ressources supplémentaires