Injection de dépendances dans ASP.NET CoreDependency injection in ASP.NET Core

Par Steve Smith et Scott AddieBy Steve Smith and Scott Addie

ASP.NET Core est conçu à la base pour prendre en charge et exploiter l’injection de dépendances.ASP.NET Core is designed from the ground up to support and leverage dependency injection. Les applications ASP.NET Core peuvent tirer parti des services de framework intégrés en les injectant dans des méthodes de la classe Startup et les services d’application peuvent également être configurés pour l’injection.ASP.NET Core applications can leverage built-in framework services by having them injected into methods in the Startup class, and application services can be configured for injection as well. Le conteneur de services par défaut fourni par ASP.NET Core offre un ensemble minimal de fonctionnalités et n’a pas vocation à remplacer d’autres conteneurs.The default services container provided by ASP.NET Core provides a minimal feature set and isn't intended to replace other containers.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)View or download sample code (how to download)

Qu’est-ce que l’injection de dépendances ?What is dependency injection?

L’injection de dépendances est une technique de couplage faible entre des objets et leurs collaborateurs, ou dépendances.Dependency injection (DI) is a technique for achieving loose coupling between objects and their collaborators, or dependencies. Au lieu d’instancier directement des collaborateurs ou d’utiliser des références statiques, les objets dont a besoin une classe pour effectuer ses opérations sont fournis à la classe d’une certaine manière.Rather than directly instantiating collaborators, or using static references, the objects a class needs in order to perform its actions are provided to the class in some fashion. Le plus souvent, les classes déclarent leurs dépendances par le biais de leur constructeur, ce qui leur permet de suivre le principe des dépendances explicites.Most often, classes will declare their dependencies via their constructor, allowing them to follow the Explicit Dependencies Principle. Cette approche est appelée « injection de constructeurs ».This approach is known as "constructor injection".

Lorsque les classes sont conçues avec l’injection de dépendances à l’esprit, leur couplage est plus faible car elles n’ont de dépendances directes codées en dur vis-à-vis de leurs collaborateurs.When classes are designed with DI in mind, they're more loosely coupled because they don't have direct, hard-coded dependencies on their collaborators. Cette conception suit le principe de l’inversion de dépendances, qui stipule que les « modules de niveau supérieur ne doivent pas dépendre de modules de niveau inférieur ; tous doivent dépendre d’abstractions ».This follows the Dependency Inversion Principle, which states that "high level modules shouldn't depend on low level modules; both should depend on abstractions." Au lieu de référencer des implémentations spécifiques, les classes demandent des abstractions (généralement interfaces) qui leur sont fournies lors de la construction de la classe.Instead of referencing specific implementations, classes request abstractions (typically interfaces) which are provided to them when the class is constructed. L’extraction de dépendances dans des interfaces et la fourniture des implémentations de ces interfaces en tant que paramètres sont également des exemples du modèle de conception de stratégie.Extracting dependencies into interfaces and providing implementations of these interfaces as parameters is also an example of the Strategy design pattern.

Lorsqu’un système est conçu pour utiliser l’injection de dépendances, avec de nombreuses classes qui demandent leurs dépendances par le biais de leur constructeur (ou leurs propriétés), il est judicieux d’avoir une classe dédiée à la création de ces classes avec leurs dépendances associées.When a system is designed to use DI, with many classes requesting their dependencies via their constructor (or properties), it's helpful to have a class dedicated to creating these classes with their associated dependencies. Ces classes sont appelés des conteneurs, ou plus particulièrement, des conteneurs d’inversion de contrôle ou des conteneurs d’injection de dépendances.These classes are referred to as containers, or more specifically, Inversion of Control (IoC) containers or Dependency Injection (DI) containers. Un conteneur est grosso modo une fabrique chargée de fournir des instances de types demandées à partir d’elle-même.A container is essentially a factory that's responsible for providing instances of types that are requested from it. Si un type donné a déclaré qu’il possède des dépendances et que le conteneur a été configuré pour fournir les types de dépendances, il crée les dépendances dans le cadre de la création de l’instance demandée.If a given type has declared that it has dependencies, and the container has been configured to provide the dependency types, it will create the dependencies as part of creating the requested instance. De cette façon, des graphiques de dépendance complexes peuvent être fournis aux classes sans exiger de construction d’objet codé en dur.In this way, complex dependency graphs can be provided to classes without the need for any hard-coded object construction. En plus de créer des objets avec leurs dépendances, les conteneurs gèrent généralement les durées de vie des objets au sein de l’application.In addition to creating objects with their dependencies, containers typically manage object lifetimes within the application.

ASP.NET Core inclut un simple conteneur intégré (représenté par l’interface IServiceProvider) qui prend en charge l’injection de constructeurs par défaut, et ASP.NET rend certains services disponibles par le biais de l’injection de dépendances.ASP.NET Core includes a simple built-in container (represented by the IServiceProvider interface) that supports constructor injection by default, and ASP.NET makes certain services available through DI. Le conteneur d’ASP.NET fait référence aux types qu’il gère comme des services.ASP.NET's container refers to the types it manages as services. Dans le reste de cet article, les services font référence aux types gérés par le conteneur d’inversion de contrôle d’ASP.NET Core.Throughout the rest of this article, services will refer to types that are managed by ASP.NET Core's IoC container. Vous configurez les services du conteneur intégré dans la méthode ConfigureServices de la classe Startup de votre application.You configure the built-in container's services in the ConfigureServices method in your application's Startup class.

Note

Martin Fowler a écrit un article complet sur les conteneurs d’inversion de contrôle et le modèle d’injection de dépendances.Martin Fowler has written an extensive article on Inversion of Control Containers and the Dependency Injection Pattern. Le groupe Microsoft Patterns and Practices fait également une excellente description de l’injection de dépendances.Microsoft Patterns and Practices also has a great description of Dependency Injection.

Note

Cet article traite de l’injection de dépendance telle qu’elle s’applique à toutes les applications ASP.NET.This article covers Dependency Injection as it applies to all ASP.NET applications. L’injection de dépendances au sein des contrôleurs MVC est abordée dans Injection de dépendances et contrôleurs.Dependency Injection within MVC controllers is covered in Dependency Injection and Controllers.

Comportement d’injection de constructeursConstructor injection behavior

L’injection de constructeurs exige que le constructeur en question soit public.Constructor injection requires that the constructor in question be public. Sinon, votre application lève une InvalidOperationException :Otherwise, your app will throw an InvalidOperationException:

Impossible de trouver un constructeur approprié pour le type 'VotreType'.A suitable constructor for type 'YourType' couldn't be located. Vérifiez que le type est concret et que des services sont inscrits pour tous les paramètres d’un constructeur public.Ensure the type is concrete and services are registered for all parameters of a public constructor.

L’injection de constructeurs exige qu’un seul constructeur applicable existe.Constructor injection requires that only one applicable constructor exist. Les surcharges de constructeurs sont prises en charge, mais une seule peut exister dont les arguments peuvent tous être satisfaits par l’injection de dépendances.Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection. S’il en existe plusieurs, votre application lève une InvalidOperationException :If more than one exists, your app will throw an InvalidOperationException:

Plusieurs constructeurs acceptant tous les types d’argument donnés ont été trouvés dans le type 'VotreType'.Multiple constructors accepting all given argument types have been found in type 'YourType'. Il ne doit y avoir qu’un seul constructeur applicable.There should only be one applicable constructor.

Les constructeurs peuvent accepter des arguments qui ne sont pas fournis par l’injection de dépendances, mais ceux-ci doivent prendre en charge les valeurs par défaut.Constructors can accept arguments that are not provided by dependency injection, but these must support default values. Exemple :For example:

// throws InvalidOperationException: Unable to resolve service for type 'System.String'...
public CharactersController(ICharacterRepository characterRepository, string title)
{
    _characterRepository = characterRepository;
    _title = title;
}

// runs without error
public CharactersController(ICharacterRepository characterRepository, string title = "Characters")
{
    _characterRepository = characterRepository;
    _title = title;
}

Utilisation des services fournis par le frameworkUsing framework-provided services

La méthode ConfigureServices dans la classe Startup est chargée de définir les services utilisés par l’application, notamment les fonctionnalités de plateforme comme Entity Framework Core et ASP.NET Core MVC.The ConfigureServices method in the Startup class is responsible for defining the services the application will use, including platform features like Entity Framework Core and ASP.NET Core MVC. Au départ, la valeur IServiceCollection fournie à ConfigureServices a les services suivants définis (en fonction de la manière dont l’hôte a été configuré) :Initially, the IServiceCollection provided to ConfigureServices has the following services defined (depending on how the host was configured):

Type de serviceService Type Durée de vieLifetime
Microsoft.AspNetCore.Hosting.IHostingEnvironmentMicrosoft.AspNetCore.Hosting.IHostingEnvironment SingletonSingleton
Microsoft.Extensions.Logging.ILoggerFactoryMicrosoft.Extensions.Logging.ILoggerFactory SingletonSingleton
Microsoft.Extensions.Logging.ILogger<T>Microsoft.Extensions.Logging.ILogger<T> SingletonSingleton
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactoryMicrosoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory TransientTransient
Microsoft.AspNetCore.Http.IHttpContextFactoryMicrosoft.AspNetCore.Http.IHttpContextFactory TransientTransient
Microsoft.Extensions.Options.IOptions<T>Microsoft.Extensions.Options.IOptions<T> SingletonSingleton
System.Diagnostics.DiagnosticSourceSystem.Diagnostics.DiagnosticSource SingletonSingleton
System.Diagnostics.DiagnosticListenerSystem.Diagnostics.DiagnosticListener SingletonSingleton
Microsoft.AspNetCore.Hosting.IStartupFilterMicrosoft.AspNetCore.Hosting.IStartupFilter TransientTransient
Microsoft.Extensions.ObjectPool.ObjectPoolProviderMicrosoft.Extensions.ObjectPool.ObjectPoolProvider SingletonSingleton
Microsoft.Extensions.Options.IConfigureOptions<T>Microsoft.Extensions.Options.IConfigureOptions<T> TransientTransient
Microsoft.AspNetCore.Hosting.Server.IServerMicrosoft.AspNetCore.Hosting.Server.IServer SingletonSingleton
Microsoft.AspNetCore.Hosting.IStartupMicrosoft.AspNetCore.Hosting.IStartup SingletonSingleton
Microsoft.AspNetCore.Hosting.IApplicationLifetimeMicrosoft.AspNetCore.Hosting.IApplicationLifetime SingletonSingleton

Voici un exemple qui montre comment ajouter des services supplémentaires au conteneur à l’aide de plusieurs méthodes d’extension comme AddDbContext, AddIdentity et AddMvc.Below is an example of how to add additional services to the container using a number of extension methods like AddDbContext, AddIdentity, and AddMvc.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}

Les fonctionnalités et l’intergiciel (middleware) fournis par ASP.NET, comme MVC, respectent une convention visant à utiliser une seule méthode d’extension AddServiceName pour inscrire tous les services requis par cette fonctionnalité.The features and middleware provided by ASP.NET, such as MVC, follow a convention of using a single AddServiceName extension method to register all of the services required by that feature.

Conseil

Vous pouvez demander certains services fournis par le framework au sein des méthodes Startup par le biais de leurs listes de paramètres. Consultez Démarrage d’une application pour plus d’informations.You can request certain framework-provided services within Startup methods through their parameter lists - see Application Startup for more details.

Inscription de servicesRegistering services

Vous pouvez inscrire vos propres services d’application comme suit.You can register your own application services as follows. Le premier type générique représente le type (en général une interface) demandé à partir du conteneur.The first generic type represents the type (typically an interface) that will be requested from the container. Le deuxième type générique représente le type concret instancié par le conteneur et utilisé pour répondre à de telles demandes.The second generic type represents the concrete type that will be instantiated by the container and used to fulfill such requests.

services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();

Note

Chaque méthode d’extension services.Add<ServiceName> ajoute (et éventuellement configure) des services.Each services.Add<ServiceName> extension method adds (and potentially configures) services. Par exemple, services.AddMvc() ajoute les services dont MVC a besoin.For example, services.AddMvc() adds the services MVC requires. Il est recommandé de suivre cette convention, en plaçant des méthodes d’extension dans l’espace de noms Microsoft.Extensions.DependencyInjection, pour encapsuler des groupes d’inscriptions de services.It's recommended that you follow this convention, placing extension methods in the Microsoft.Extensions.DependencyInjection namespace, to encapsulate groups of service registrations.

La méthode AddTransient est utilisée pour mapper des types abstraits sur des services concrets instanciés séparément pour chaque objet qui en a besoin.The AddTransient method is used to map abstract types to concrete services that are instantiated separately for every object that requires it. Il s’agit de la durée de vie du service. D’autres options de durée de vie sont décrites ci-dessous.This is known as the service's lifetime, and additional lifetime options are described below. Il est important de choisir une durée de vie appropriée pour chacun des services que vous inscrivez.It's important to choose an appropriate lifetime for each of the services you register. Une nouvelle instance du service doit-elle être fournie à chaque classe qui le demande ?Should a new instance of the service be provided to each class that requests it? Une seule instance doit-elle être utilisée tout au long d’une requête web donnée ?Should one instance be used throughout a given web request? Ou une seule instance doit-elle être utilisée pendant la durée de vie de l’application ?Or should a single instance be used for the lifetime of the application?

Dans l’exemple de cet article, il existe un simple contrôleur qui affiche les noms des caractères, appelé CharactersController.In the sample for this article, there's a simple controller that displays character names, called CharactersController. Sa méthode Index affiche la liste actuelle des caractères stockés dans l’application et initialise la collection avec quelques caractères, s’il n’en existe aucun.Its Index method displays the current list of characters that have been stored in the application, and initializes the collection with a handful of characters if none exist. Notez que bien que cette application utilise Entity Framework Core et la classe ApplicationDbContext pour sa persistance, rien de tout cela n’apparaît dans le contrôleur.Note that although this application uses Entity Framework Core and the ApplicationDbContext class for its persistence, none of that's apparent in the controller. Au lieu de cela, le mécanisme d’accès aux données spécifique est rendu abstrait derrière une interface, ICharacterRepository, qui suit le modèle de référentiel.Instead, the specific data access mechanism has been abstracted behind an interface, ICharacterRepository, which follows the repository pattern. Une instance de ICharacterRepository est demandée via le constructeur et attribuée à un champ privé, qui est ensuite utilisé pour accéder aux caractères selon les besoins.An instance of ICharacterRepository is requested via the constructor and assigned to a private field, which is then used to access characters as necessary.

public class CharactersController : Controller
{
    private readonly ICharacterRepository _characterRepository;

    public CharactersController(ICharacterRepository characterRepository)
    {
        _characterRepository = characterRepository;
    }

    // GET: /characters/
    public IActionResult Index()
    {
        PopulateCharactersIfNoneExist();
        var characters = _characterRepository.ListAll();

        return View(characters);
    }
    
    private void PopulateCharactersIfNoneExist()
    {
        if (!_characterRepository.ListAll().Any())
        {
            _characterRepository.Add(new Character("Darth Maul"));
            _characterRepository.Add(new Character("Darth Vader"));
            _characterRepository.Add(new Character("Yoda"));
            _characterRepository.Add(new Character("Mace Windu"));
        }
    }
}

ICharacterRepository définit les deux méthodes dont le contrôleur a besoin pour utiliser les instances de Character.The ICharacterRepository defines the two methods the controller needs to work with Character instances.

using System.Collections.Generic;
using DependencyInjectionSample.Models;

namespace DependencyInjectionSample.Interfaces
{
    public interface ICharacterRepository
    {
        IEnumerable<Character> ListAll();
        void Add(Character character);
    }
}

Cette interface est à son tour implémentée par un type concret, CharacterRepository, qui est utilisé au moment de l’exécution.This interface is in turn implemented by a concrete type, CharacterRepository, that's used at runtime.

Note

La façon dont l’injection de dépendances est utilisée avec la classe CharacterRepository est un modèle général que vous pouvez suivre pour tous vos services d’application, pas seulement dans les « référentiels » ou les classes d’accès aux données.The way DI is used with the CharacterRepository class is a general model you can follow for all of your application services, not just in "repositories" or data access classes.

using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Models
{
    public class CharacterRepository : ICharacterRepository
    {
        private readonly ApplicationDbContext _dbContext;

        public CharacterRepository(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public IEnumerable<Character> ListAll()
        {
            return _dbContext.Characters.AsEnumerable();
        }

        public void Add(Character character)
        {
            _dbContext.Characters.Add(character);
            _dbContext.SaveChanges();
        }
    }
}

Notez que CharacterRepository exige un ApplicationDbContext dans son constructeur.Note that CharacterRepository requests an ApplicationDbContext in its constructor. Il n’est pas rare que l’injection de dépendances soit utilisée de manière chaînée comme cela, avec chaque dépendance demandée demandant à son tour ses propres dépendances.It's not unusual for dependency injection to be used in a chained fashion like this, with each requested dependency in turn requesting its own dependencies. Le conteneur est chargé de résoudre toutes les dépendances dans le graphique et de retourner le service entièrement résolu.The container is responsible for resolving all of the dependencies in the graph and returning the fully resolved service.

Note

La création de l’objet demandé, et de tous les objets dont il a besoin, et de tous les objets dont ceux-ci ont besoin, est parfois appelée graphique d’objet.Creating the requested object, and all of the objects it requires, and all of the objects those require, is sometimes referred to as an object graph. De même, l’ensemble collectif de dépendances qui doivent être résolues est généralement appelé arborescence des dépendances ou graphique de dépendance.Likewise, the collective set of dependencies that must be resolved is typically referred to as a dependency tree or dependency graph.

Dans ce cas, à la fois ICharacterRepository et à son tour ApplicationDbContext doivent être inscrits auprès du conteneur de services dans ConfigureServices dans Startup.In this case, both ICharacterRepository and in turn ApplicationDbContext must be registered with the services container in ConfigureServices in Startup. ApplicationDbContext est configuré avec l’appel à la méthode d’extension AddDbContext<T>.ApplicationDbContext is configured with the call to the extension method AddDbContext<T>. Le code suivant illustre l’inscription du type CharacterRepository.The following code shows the registration of the CharacterRepository type.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseInMemoryDatabase()
    );

    // Add framework services.
    services.AddMvc();

    // Register application services.
    services.AddScoped<ICharacterRepository, CharacterRepository>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
    services.AddTransient<OperationService, OperationService>();
}

Vous devez ajouter des contextes Entity Framework au conteneur de services en utilisant la durée de vie Scoped.Entity Framework contexts should be added to the services container using the Scoped lifetime. Cet ajout est automatique si vous utilisez les méthodes d’assistance comme indiqué ci-dessus.This is taken care of automatically if you use the helper methods as shown above. Les référentiels qui utilisent Entity Framework doivent utiliser la même durée de vie.Repositories that will make use of Entity Framework should use the same lifetime.

Avertissement

Le principal danger à surveiller a trait à la résolution d’un service Scoped depuis un singleton.The main danger to be wary of is resolving a Scoped service from a singleton. Il est probable dans ce cas que l’état du service ne soit pas correct lors du traitement des requêtes suivantes.It's likely in such a case that the service will have incorrect state when processing subsequent requests.

Les services qui ont des dépendances doivent les inscrire dans le conteneur.Services that have dependencies should register them in the container. Si le constructeur d’un service exige une primitive, comme string, celle-ci peut être injectée à l’aide de la configuration et du modèle d’options.If a service's constructor requires a primitive, such as a string, this can be injected by using configuration and the options pattern.

Durée de vie du service et options d’inscriptionService lifetimes and registration options

Vous pouvez configurer les services ASP.NET avec les durées de vie suivantes :ASP.NET services can be configured with the following lifetimes:

TransientTransient

Des services à durée de vie temporaire (Transient) sont créés chaque fois qu’ils sont demandés.Transient lifetime services are created each time they're requested. Cette durée de vie convient parfaitement aux services légers et sans état.This lifetime works best for lightweight, stateless services.

ScopedScoped

Les services à durée de vie délimitée (Scoped) sont créés une seule fois par requête.Scoped lifetime services are created once per request.

Avertissement

Si vous utilisez un service Scoped dans un middleware, injectez le service dans la méthode Invoke ou InvokeAsync.If you're using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Ne faites pas l’injection via l’injection du constructeur, car elle force le service à se comporter comme un singleton.Don't inject via constructor injection because it forces the service to behave like a singleton.

SingletonSingleton

Les services à durée de vie Singleton sont créés la première fois qu’ils sont demandés (ou lorsque ConfigureServices est exécuté si vous y spécifiez une instance), puis chaque requête suivante utilise la même instance.Singleton lifetime services are created the first time they're requested (or when ConfigureServices is run if you specify an instance there) and then every subsequent request will use the same instance. Si votre application exige un comportement Singleton, il est recommandé d’autoriser le conteneur de services à gérer la durée de vie du service au lieu d’implémenter le modèle de conception Singleton et de gérer la durée de vie de votre objet dans la classe vous-même.If your application requires singleton behavior, allowing the services container to manage the service's lifetime is recommended instead of implementing the singleton design pattern and managing your object's lifetime in the class yourself.

Vous pouvez inscrire des services auprès du conteneur de plusieurs façons.Services can be registered with the container in several ways. Nous avons déjà vu comment inscrire une implémentation de service avec un type donné en spécifiant le type concret à utiliser.We have already seen how to register a service implementation with a given type by specifying the concrete type to use. En outre, une fabrique peut être spécifiée. Elle est ensuite utilisée pour créer l’instance à la demande.In addition, a factory can be specified, which will then be used to create the instance on demand. La troisième méthode consiste à spécifier directement l’instance du type à utiliser, auquel cas le conteneur n’essaie jamais de créer une instance (ni d’en disposer).The third approach is to directly specify the instance of the type to use, in which case the container will never attempt to create an instance (nor will it dispose of the instance).

Pour illustrer la différence entre ces options de durée de vie et d’inscription, considérez une interface simple qui représente une ou plusieurs tâches en tant qu’opération avec un identificateur unique, OperationId.To demonstrate the difference between these lifetime and registration options, consider a simple interface that represents one or more tasks as an operation with a unique identifier, OperationId. Selon la façon dont nous configurons la durée de vie de ce service, le conteneur fournit les mêmes instances ou des instances différentes du service à la classe qui effectue la requête.Depending on how we configure the lifetime for this service, the container will provide either the same or different instances of the service to the requesting class. Pour indiquer clairement quelle durée de vie est demandée, nous allons créer un seul type par option de durée de vie :To make it clear which lifetime is being requested, we will create one type per lifetime option:

using System;

namespace DependencyInjectionSample.Interfaces
{
    public interface IOperation
    {
        Guid OperationId { get; }
    }

    public interface IOperationTransient : IOperation
    {
    }
    public interface IOperationScoped : IOperation
    {
    }
    public interface IOperationSingleton : IOperation
    {
    }
    public interface IOperationSingletonInstance : IOperation
    {
    }
}

Nous implémentons ces interfaces à l’aide d’une seule classe, Operation, qui accepte un Guid dans son constructeur ou utilise un nouveau Guid si aucun n’est fourni.We implement these interfaces using a single class, Operation, that accepts a Guid in its constructor, or uses a new Guid if none is provided.

Ensuite, dans ConfigureServices, chaque type est ajouté au conteneur en fonction de sa durée de vie nommée :Next, in ConfigureServices, each type is added to the container according to its named lifetime:

    services.AddScoped<ICharacterRepository, CharacterRepository>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
    services.AddTransient<OperationService, OperationService>();
}

Notez que le service IOperationSingletonInstance utilise une instance spécifique avec un ID connu de Guid.Empty pour que l’utilisation de ce type soit clairement indiquée (son GUID ne contient que des zéros).Note that the IOperationSingletonInstance service is using a specific instance with a known ID of Guid.Empty so it will be clear when this type is in use (its Guid will be all zeroes). Nous avons également inscrit un OperationService qui dépend de chacun des autres types Operation, pour qu’il soit clairement déterminé au sein d’une demande si ce service obtient la même instance que le contrôleur, ou une nouvelle, pour chaque type d’opération.We have also registered an OperationService that depends on each of the other Operation types, so that it will be clear within a request whether this service is getting the same instance as the controller, or a new one, for each operation type. Ce service se contente d’exposer ses dépendances comme des propriétés, afin de pouvoir les présenter dans l’affichage.All this service does is expose its dependencies as properties, so they can be displayed in the view.

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
    public class OperationService
    {
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }

        public OperationService(IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance instanceOperation)
        {
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = instanceOperation;
        }
    }
}

Pour illustrer les durées de vie des objets dans et entre les requêtes individuelles distinctes faites à l’application, l’exemple inclut un OperationsController qui demande chaque type IOperation ainsi qu’un OperationService.To demonstrate the object lifetimes within and between separate individual requests to the application, the sample includes an OperationsController that requests each kind of IOperation type as well as an OperationService. L’action Index affiche alors toutes les valeurs OperationId du contrôleur et du service.The Index action then displays all of the controller's and service's OperationId values.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
    public class OperationsController : Controller
    {
        private readonly OperationService _operationService;
        private readonly IOperationTransient _transientOperation;
        private readonly IOperationScoped _scopedOperation;
        private readonly IOperationSingleton _singletonOperation;
        private readonly IOperationSingletonInstance _singletonInstanceOperation;

        public OperationsController(OperationService operationService,
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance singletonInstanceOperation)
        {
            _operationService = operationService;
            _transientOperation = transientOperation;
            _scopedOperation = scopedOperation;
            _singletonOperation = singletonOperation;
            _singletonInstanceOperation = singletonInstanceOperation;
        }

        public IActionResult Index()
        {
            // viewbag contains controller-requested services
            ViewBag.Transient = _transientOperation;
            ViewBag.Scoped = _scopedOperation;
            ViewBag.Singleton = _singletonOperation;
            ViewBag.SingletonInstance = _singletonInstanceOperation;
            
            // operation service has its own requested services
            ViewBag.Service = _operationService;
            return View();
        }
    }
}

Maintenant, deux requêtes distinctes sont effectuées auprès de cette action de contrôleur :Now two separate requests are made to this controller action:

Affichage des opérations de l’exemple d’application web d’injection de dépendances en cours d’exécution dans Microsoft Edge présentant les valeurs d’ID d’opération (GUID) des opérations Transient, Scoped, Singleton du contrôleur d’instance et du service d’opération pour la première requête.

Affichage des opérations montrant les valeurs d’ID d’opération pour une deuxième requête.

Observez les valeurs OperationId qui varient au sein d’une requête et entre les requêtes.Observe which of the OperationId values vary within a request, and between requests.

  • Les objets Transient sont toujours différents ; une nouvelle instance est fournie à chaque contrôleur et à chaque service.Transient objects are always different; a new instance is provided to every controller and every service.

  • Les objets Scoped sont les mêmes au sein d’une requête, mais ils diffèrent entre les différentes requêtes.Scoped objects are the same within a request, but different across different requests

  • Les objets Singleton sont les mêmes pour chaque objet et chaque requête (qu’une instance soit fournie dans ConfigureServices ou non).Singleton objects are the same for every object and every request (regardless of whether an instance is provided in ConfigureServices)

Résoudre un service Scoped dans le scope de l’applicationResolve a scoped service within the application scope

Créez un IServiceScope avec IServiceScopeFactory.CreateScope pour résoudre un service Scoped dans le scope de l’application.Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app's scope. Cette approche est pratique pour accéder à un service Scoped au démarrage pour exécuter des tâches d’initialisation.This approach is useful to access a scoped service at startup to run initialization tasks. L’exemple suivant montre comment obtenir un contexte pour MyScopedService dans Program.Main :The following example shows how to obtain a context for the MyScopedService in Program.Main:

public static void Main(string[] args)
{
    var host = BuildWebHost(args);

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

        try
        {
            var serviceContext = services.GetRequiredService<MyScopedService>();
            // Use the context here
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred.");
        }
    }

    host.Run();
}

Validation de l’étendueScope validation

Quand l’application s’exécute dans l’environnement de développement sur ASP.NET Core 2.0 ou ultérieur, le fournisseur de services par défaut effectue des contrôles pour vérifier que :When the app is running in the Development environment on ASP.NET Core 2.0 or later, the default service provider performs checks to verify that:

  • Les services Scoped ne sont pas résolus directement ou indirectement à partir du fournisseur de services racine.Scoped services aren't directly or indirectly resolved from the root service provider.
  • Les services Scoped ne sont pas directement ou indirectement injectés dans des singletons.Scoped services aren't directly or indirectly injected into singletons.

Le fournisseur de services racine est créé quand BuildServiceProvider est appelé.The root service provider is created when BuildServiceProvider is called. La durée de vie du fournisseur de services racine correspond à la durée de vie de l’application/du serveur quand le fournisseur démarre avec l’application et qu’il est supprimé quand l’application s’arrête.The root service provider's lifetime corresponds to the app/server's lifetime when the provider starts with the app and is disposed when the app shuts down.

Les services Scoped sont supprimés par le conteneur qui les a créés.Scoped services are disposed by the container that created them. Si un service Scoped est créé dans le conteneur racine, la durée de vie du service est promue en singleton, car elle est supprimée par le conteneur racine seulement quand l’application/le serveur est arrêté.If a scoped service is created in the root container, the service's lifetime is effectively promoted to singleton because it's only disposed by the root container when app/server is shut down. La validation des étendues du service permet de traiter ces situations quand BuildServiceProvider est appelé.Validating service scopes catches these situations when BuildServiceProvider is called.

Pour plus d’informations, consultez la rubrique Validation des étendues dans l’hôte web.For more information, see Scope validation in the Web Host topic.

Services de requêteRequest Services

Les services disponibles au sein d’une requête ASP.NET à partir de HttpContext sont exposés par le biais de la collection RequestServices.The services available within an ASP.NET request from HttpContext are exposed through the RequestServices collection.

Boîte de dialogue contextuelle Intellisense des services de requête HttpContext indiquant que les services de requête obtiennent ou définissent la valeur IServiceProvider qui fournit l’accès au conteneur de services de la requête.

Les services de requête représentent les services que vous configurez et demandez dans le cadre de votre application.Request Services represent the services you configure and request as part of your application. Lorsque vos objets spécifient des dépendances, ceux-ci sont satisfaits par les types trouvés dans RequestServices, pas dans ApplicationServices.When your objects specify dependencies, these are satisfied by the types found in RequestServices, not ApplicationServices.

En règle générale, vous ne devez pas utiliser ces propriétés directement, mais plutôt préférer demander les types dont vos classes ont besoin par le biais du constructeur de votre classe et laisser le framework injecter ces dépendances.Generally, you shouldn't use these properties directly, preferring instead to request the types your classes you require via your class's constructor, and letting the framework inject these dependencies. Vous obtenez ainsi des classes plus faciles à tester (consultez Tester et déboguer) et plus faiblement couplées.This yields classes that are easier to test (see Test and debug) and are more loosely coupled.

Note

Préférez demander des dépendances en tant que paramètres de constructeur plutôt qu’accéder à la collection RequestServices.Prefer requesting dependencies as constructor parameters to accessing the RequestServices collection.

Conception de services pour l’injection de dépendancesDesigning services for dependency injection

Vous devez concevoir vos services pour qu’ils utilisent l’injection de dépendances afin d’obtenir leurs collaborateurs.You should design your services to use dependency injection to get their collaborators. Cela signifie que vous devez éviter d’utiliser des appels de méthode statique avec état (qui entraînent un « code smell » appelé « static cling ») et d’instancier directement des classes dépendantes au sein de vos services.This means avoiding the use of stateful static method calls (which result in a code smell known as static cling) and the direct instantiation of dependent classes within your services. Cela peut vous aider de mémoriser l’expression « New is Glue », quand vous choisissez d’instancier ou non un type ou de le demander par le biais de l’injection de dépendances.It may help to remember the phrase, New is Glue, when choosing whether to instantiate a type or to request it via dependency injection. En suivant les principes SOLID de conception orientée objet, vos classes ont naturellement tendance à être petites, bien factorisées et facilement testées.By following the SOLID Principles of Object Oriented Design, your classes will naturally tend to be small, well-factored, and easily tested.

Que se passe-t-il si vous trouvez que vos classes ont tendance à avoir trop de dépendances injectées ?What if you find that your classes tend to have way too many dependencies being injected? C’est généralement le signe que votre classe essaie d’en faire trop et qu’elle enfreint probablement le principe de responsabilité unique.This is generally a sign that your class is trying to do too much, and is probably violating SRP - the Single Responsibility Principle. Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans une nouvelle classe.See if you can refactor the class by moving some of its responsibilities into a new class. N’oubliez pas que vos classes Controller doivent se concentrer sur les préoccupations de l’interface utilisateur, donc les règles d’entreprise et les détails d’implémentation de l’accès aux données doivent être conservés dans les classes appropriées à ces préoccupations distinctes.Keep in mind that your Controller classes should be focused on UI concerns, so business rules and data access implementation details should be kept in classes appropriate to these separate concerns.

En ce qui concerne l’accès aux données en particulier, vous pouvez injecter DbContext dans vos contrôleurs (en supposant que vous ayez ajouté EF au conteneur de services dans ConfigureServices).With regards to data access specifically, you can inject the DbContext into your controllers (assuming you've added EF to the services container in ConfigureServices). Certains développeurs préfèrent utiliser une interface de référentiel pour la base de données plutôt que d’injecter directement DbContext.Some developers prefer to use a repository interface to the database rather than injecting the DbContext directly. L’utilisation d’une interface pour encapsuler la logique d’accès aux données à un seul emplacement permet de réduire le nombre d’emplacements à modifier quand votre base de données évolue.Using an interface to encapsulate the data access logic in one place can minimize how many places you will have to change when your database changes.

Mise à disposition des servicesDisposing of services

Le conteneur appelle Dispose pour les types IDisposable qu’il crée.The container will call Dispose for IDisposable types it creates. Toutefois, si vous ajoutez une instance au conteneur vous-même, elle n’est pas mise à disposition.However, if you add an instance to the container yourself, it will not be disposed.

Exemple :Example:

// Services implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}


public void ConfigureServices(IServiceCollection services)
{
    // container will create the instance(s) of these types and will dispose them
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());

    // container didn't create instance so it will NOT dispose it
    services.AddSingleton<Service3>(new Service3());
    services.AddSingleton(new Service3());
}

Note

Dans la version 1.0, le conteneur appelé dispose de tous les objets IDisposable, y compris de ceux qu’il n’a pas créés.In version 1.0, the container called dispose on all IDisposable objects, including those it didn't create.

Remplacement du conteneur de services par défautReplacing the default services container

Le conteneur de services intégré a vocation à répondre aux besoins élémentaires du framework et de la plupart des applications consommatrices qui s’appuient sur ce dernier.The built-in services container is meant to serve the basic needs of the framework and most consumer applications built on it. Toutefois, les développeurs peuvent remplacer le conteneur intégré par leur conteneur préféré.However, developers can replace the built-in container with their preferred container. La méthode ConfigureServices retourne généralement void, mais si sa signature est modifiée afin de retourner IServiceProvider, un autre conteneur peut être configuré et retourné.The ConfigureServices method typically returns void, but if its signature is changed to return IServiceProvider, a different container can be configured and returned. Il existe de nombreux conteneurs d’inversion de contrôle disponibles pour .NET.There are many IOC containers available for .NET. Dans cet exemple, le package Autofac est utilisé.In this example, the Autofac package is used.

Tout d’abord, installez les packages de conteneurs appropriés :First, install the appropriate container package(s):

  • Autofac
  • Autofac.Extensions.DependencyInjection

Ensuite, configurez le conteneur dans ConfigureServices et retournez un IServiceProvider :Next, configure the container in ConfigureServices and return an IServiceProvider:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    // Add other framework services

    // Add Autofac
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}

Note

Lorsque vous utilisez un conteneur d’injection de dépendances tiers, vous devez modifier ConfigureServices afin de retourner IServiceProvider au lieu de void.When using a third-party DI container, you must change ConfigureServices so that it returns IServiceProvider instead of void.

Enfin, configurez Autofac normalement dans DefaultModule :Finally, configure Autofac as normal in DefaultModule:

public class DefaultModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
    }
}

Au moment de l’exécution, Autofac est utilisé pour résoudre les types et injecter des dépendances.At runtime, Autofac will be used to resolve types and inject dependencies. En savoir plus sur l’utilisation d’Autofac et d’ASP.NET Core.Learn more about using Autofac and ASP.NET Core.

Sécurité des threadsThread safety

Les services singleton doivent être thread-safe.Singleton services need to be thread safe. Si un service singleton a une dépendance vis-à-vis d’un service Transient, ce dernier peut également avoir besoin d’être thread-safe selon la manière dont il est utilisé par le singleton.If a singleton service has a dependency on a transient service, the transient service may also need to be thread safe depending how it's used by the singleton.

RecommandationsRecommendations

Lorsque vous utilisez l’injection de dépendances, gardez à l’esprit les recommandations suivantes :When working with dependency injection, keep the following recommendations in mind:

  • L’injection de dépendances est destinée aux objets qui ont des dépendances complexes.DI is for objects that have complex dependencies. Les contrôleurs, les services, les adaptateurs et les référentiels sont tous des exemples d’objets susceptibles d’être ajoutés à l’injection de dépendances.Controllers, services, adapters, and repositories are all examples of objects that might be added to DI.

  • Évitez de stocker des données et des configurations directement dans l’injection de dépendances.Avoid storing data and configuration directly in DI. Par exemple, le panier d’achat d’un utilisateur ne doit en général pas être ajouté au conteneur de services.For example, a user's shopping cart shouldn't typically be added to the services container. La configuration doit utiliser le modèle d’options.Configuration should use the options pattern. De même, évitez les objets « conteneurs de données » qui n’existent que pour autoriser l’accès à un autre objet.Similarly, avoid "data holder" objects that only exist to allow access to some other object. Il est préférable de demander l’élément réel dont vous avez besoin par le biais de l’injection de dépendances, si possible.It's better to request the actual item needed via DI, if possible.

  • Évitez l’accès statique aux services.Avoid static access to services.

  • Évitez l’emplacement du service dans votre code d’application.Avoid service location in your application code.

  • Évitez l’accès statique à HttpContext.Avoid static access to HttpContext.

Comme pour toutes les recommandations, vous pouvez vous trouver dans des situations où il est nécessaire d’en ignorer une.Like all sets of recommendations, you may encounter situations where ignoring one is required. À notre sens, ces exceptions sont rares. Elles concernent principalement des cas très spéciaux au sein du framework lui-même.We have found exceptions to be rare -- mostly very special cases within the framework itself.

L’injection de dépendances constitue une alternative aux modèles d’accès aux objets statiques/globaux.Dependency injection is an alternative to static/global object access patterns. 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.You may not be able to realize the benefits of DI if you mix it with static object access.

Ressources supplémentairesAdditional resources