Injection de dépendances dans les contrôleurs dans ASP.NET CoreDependency injection into controllers in ASP.NET Core

Par Steve SmithBy Steve Smith

Les contrôleurs ASP.NET Core MVC doivent demander leurs dépendances explicitement via leurs constructeurs.ASP.NET Core MVC controllers should request their dependencies explicitly via their constructors. Dans certains cas, les actions d’un contrôleur individuel peuvent nécessiter un service, et il peut ne pas être judicieux de faire les demandes au niveau du contrôleur.In some instances, individual controller actions may require a service, and it may not make sense to request at the controller level. Dans ce cas, vous pouvez également choisir d’injecter un service comme paramètre sur la méthode d’action.In this case, you can also choose to inject a service as a parameter on the action method.

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

Injection de dépendancesDependency Injection

L’injection de dépendances est une technique qui suit le principe d’inversion de dépendance et permet de composer des applications avec des modules faiblement couplés.Dependency injection is a technique that follows the Dependency Inversion Principle, allowing for applications to be composed of loosely coupled modules. ASP.NET Core a une prise en charge intégrée de l’injection de dépendances, ce qui facilite le test et la gestion des applications.ASP.NET Core has built-in support for dependency injection, which makes applications easier to test and maintain.

Injection de constructeursConstructor Injection

La prise en charge intégrée dans ASP.NET Core de l’injection de dépendances basée sur les constructeurs s’étend aux contrôleurs MVC.ASP.NET Core's built-in support for constructor-based dependency injection extends to MVC controllers. En ajoutant simplement un type de service à votre contrôleur en tant que paramètre de constructeur, ASP.NET Core tente de résoudre ce type en utilisant son conteneur de services intégrés.By simply adding a service type to your controller as a constructor parameter, ASP.NET Core will attempt to resolve that type using its built in service container. Les services sont, en général mais pas toujours, définis en utilisant des interfaces.Services are typically, but not always, defined using interfaces. Par exemple, si votre application a une logique métier qui dépend de l’heure, vous pouvez injecter un service qui récupère l’heure (au lieu de la coder en dur), ce qui permet à vos tests de réussir dans des implémentations qui utilisent une heure définie.For example, if your application has business logic that depends on the current time, you can inject a service that retrieves the time (rather than hard-coding it), which would allow your tests to pass in implementations that use a set time.

using System;

namespace ControllerDI.Interfaces
{
    public interface IDateTime
    {
        DateTime Now { get; }
    }
}

L’implémentation d’une interface comme celle-ci pour qu’elle utilise l’horloge système lors de l’exécution est simple :Implementing an interface like this one so that it uses the system clock at runtime is trivial:

using System;
using ControllerDI.Interfaces;

namespace ControllerDI.Services
{
    public class SystemDateTime : IDateTime
    {
        public DateTime Now
        {
            get { return DateTime.Now; }
        }
    }
}

Ceci étant en place, nous pouvons utiliser le service dans notre contrôleur.With this in place, we can use the service in our controller. Dans ce cas, nous avons ajouté de la logique pour la méthode HomeController Index permettant d’afficher un message d’accueil à l’utilisateur en fonction de l’heure du jour.In this case, we have added some logic to the HomeController Index method to display a greeting to the user based on the time of day.

using ControllerDI.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace ControllerDI.Controllers
{
    public class HomeController : Controller
    {
        private readonly IDateTime _dateTime;

        public HomeController(IDateTime dateTime)
        {
            _dateTime = dateTime;
        }

        public IActionResult Index()
        {
            var serverTime = _dateTime.Now;
            if (serverTime.Hour < 12)
            {
                ViewData["Message"] = "It's morning here - Good Morning!";
            }
            else if (serverTime.Hour < 17)
            {
                ViewData["Message"] = "It's afternoon here - Good Afternoon!";
            }
            else
            {
                ViewData["Message"] = "It's evening here - Good Evening!";
            }
            return View();
        }
    }
}

Si nous exécutons l’application maintenant, nous allons très probablement rencontrer une erreur :If we run the application now, we will most likely encounter an error:

An unhandled exception occurred while processing the request.

InvalidOperationException: Unable to resolve service for type 'ControllerDI.Interfaces.IDateTime' while attempting to activate 'ControllerDI.Controllers.HomeController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)

Cette erreur se produit si nous n’avons pas configuré un service dans la méthode ConfigureServices de notre classe Startup.This error occurs when we have not configured a service in the ConfigureServices method in our Startup class. Pour spécifier que les demandes de IDateTime doivent être résolues en utilisant une instance de SystemDateTime, ajoutez la ligne en surbrillance dans la liste ci-dessous à votre méthode ConfigureServices :To specify that requests for IDateTime should be resolved using an instance of SystemDateTime, add the highlighted line in the listing below to your ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    // Add application services.
    services.AddTransient<IDateTime, SystemDateTime>();
}

Note

Ce service particulier peut être implémenté en utilisant plusieurs options de durée de vie différentes (Transient, Scoped ou Singleton).This particular service could be implemented using any of several different lifetime options (Transient, Scoped, or Singleton). Consultez Injection de dépendances pour comprendre comment chacune de ces options d’étendue affecte le comportement de votre service.See Dependency Injection to understand how each of these scope options will affect the behavior of your service.

Une fois que le service a été configuré, l’exécution de l’application et la navigation jusqu’à la page d’accueil doivent afficher le message basé sur l’heure comme attendu :Once the service has been configured, running the application and navigating to the home page should display the time-based message as expected:

Message d’accueil du serveur

Conseil

Consultez Tester la logique du contrôleur pour découvrir comment demander explicitement les dépendances (http://deviq.com/explicit-dependencies-principle/) dans les contrôleurs afin de rendre le code plus facile à tester.See Test controller logic to learn how to explicitly request dependencies http://deviq.com/explicit-dependencies-principle/ in controllers makes code easier to test.

L’injection de dépendances intégrée d’ASP.NET Core ne prend en charge qu’un seul constructeur pour les classes demandant des services.ASP.NET Core's built-in dependency injection supports having only a single constructor for classes requesting services. Si vous avez plusieurs constructeurs, vous pouvez recevoir une exception indiquant :If you have more than one constructor, you may get an exception stating:

An unhandled exception occurred while processing the request.

InvalidOperationException: Multiple constructors accepting all given argument types have been found in type 'ControllerDI.Controllers.HomeController'. There should only be one applicable constructor.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type instanceType, Type[] argumentTypes, ConstructorInfo& matchingConstructor, Nullable`1[]& parameterMap)

Comme le message d’erreur l’indique, vous pouvez corriger ce problème en n’ayant qu’un seul constructeur.As the error message states, you can correct this problem having just a single constructor. Vous pouvez également remplacer la prise en charge de l’injection de dépendances par défaut par une implémentation de tiers, la plupart de ces implémentations prenant en charge plusieurs constructeurs.You can also replace the default dependency injection support with a third party implementation, many of which support multiple constructors.

Injection d’actions avec FromServicesAction Injection with FromServices

Parfois, vous n’avez pas besoin d’un service pour plusieurs actions dans votre contrôleur.Sometimes you don't need a service for more than one action within your controller. Dans ce cas, il peut être judicieux d’injecter le service comme paramètre de la méthode d’action.In this case, it may make sense to inject the service as a parameter to the action method. Vous faites cela en marquant le paramètre avec l’attribut [FromServices], comme illustré ici :This is done by marking the parameter with the attribute [FromServices] as shown here:

public IActionResult About([FromServices] IDateTime dateTime)
{
    ViewData["Message"] = "Currently on the server the time is " + dateTime.Now;

    return View();
}

Accès aux paramètres à partir d’un contrôleurAccessing Settings from a Controller

L’accès aux paramètres de configuration ou d’application à partir d’un contrôleur est un modèle commun.Accessing application or configuration settings from within a controller is a common pattern. Cet accès doit utiliser le modèle Options décrit dans Configuration.This access should use the Options pattern described in configuration. D’une façon générale, vous ne devez pas demander les paramètres directement à partir de votre contrôleur avec l’injection de dépendances.You generally shouldn't request settings directly from your controller using dependency injection. Une meilleure approche consiste à demander une instance de IOptions<T>, où T est la classe de configuration dont vous avez besoin.A better approach is to request an IOptions<T> instance, where T is the configuration class you need.

Pour utiliser le modèle Options, vous devez créer une classe qui représente les options, comme celle-ci :To work with the options pattern, you need to create a class that represents the options, such as this one:

namespace ControllerDI.Model
{
    public class SampleWebSettings
    {
        public string Title { get; set; }
        public int Updates { get; set; }
    }
}

Vous devez ensuite configurer l’application pour qu’elle utilise le modèle Options et ajouter votre classe de configuration à la collection de services dans ConfigureServices :Then you need to configure the application to use the options model and add your configuration class to the services collection in ConfigureServices:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("samplewebsettings.json");
    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; set; }

// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
    // Required to use the Options<T> pattern
    services.AddOptions();

    // Add settings from configuration
    services.Configure<SampleWebSettings>(Configuration);

    // Uncomment to add settings from code
    //services.Configure<SampleWebSettings>(settings =>
    //{
    //    settings.Updates = 17;
    //});

    services.AddMvc();

    // Add application services.
    services.AddTransient<IDateTime, SystemDateTime>();
}

Note

Dans la liste ci-dessus, nous configurons l’application pour qu’elle lise les paramètres dans un fichier au format JSON.In the above listing, we are configuring the application to read the settings from a JSON-formatted file. Vous pouvez également configurer les paramètres entièrement dans le code, comme illustré dans le code commenté ci-dessus.You can also configure the settings entirely in code, as is shown in the commented code above. Consultez Configuration pour d’autres options de configuration.See Configuration for further configuration options.

Une fois que vous avez spécifié un objet de configuration fortement typé (dans ce cas, SampleWebSettings) et que vous l’avez ajouté à la collection de services, vous pouvez le demander à partir de n’importe quelle méthode de contrôleur ou d’action en demandant une instance de IOptions<T> (dans ce cas, IOptions<SampleWebSettings>).Once you've specified a strongly-typed configuration object (in this case, SampleWebSettings) and added it to the services collection, you can request it from any Controller or Action method by requesting an instance of IOptions<T> (in this case, IOptions<SampleWebSettings>). Le code suivant montre comment demander les paramètres à partir d’un contrôleur :The following code shows how one would request the settings from a controller:

public class SettingsController : Controller
{
    private readonly SampleWebSettings _settings;

    public SettingsController(IOptions<SampleWebSettings> settingsOptions)
    {
        _settings = settingsOptions.Value;
    }

    public IActionResult Index()
    {
        ViewData["Title"] = _settings.Title;
        ViewData["Updates"] = _settings.Updates;
        return View();
    }
}

Suivre le modèle Options permet de découpler les paramètres et la configuration, et garantit que le contrôleur est conforme à la séparation des problèmes, car il n’a pas besoin de savoir ni comment ni où rechercher les informations des paramètres.Following the Options pattern allows settings and configuration to be decoupled from one another, and ensures the controller is following separation of concerns, since it doesn't need to know how or where to find the settings information. Cela simplifie également les tests unitaires du contrôleur (Tester la logique du contrôleur), car il n’y a pas de couplage des fonctionnalités statiques ni d’instanciation directe de classes de paramètres au sein de la classe du contrôleur.It also makes the controller easier to unit test Test controller logic, since there's no static cling or direct instantiation of settings classes within the controller class.