Recherche…


Introduction

Le noyau Aspnet est construit avec l'injection de dépendance comme l'un de ses concepts clés. Il introduit une abstraction de conteneur conforme afin que vous puissiez remplacer celle intégrée par un conteneur tiers de votre choix.

Syntaxe

  • IServiceCollection.Add(ServiceDescriptor item);
  • IServiceCollection.AddScoped(Type serviceType);
  • IServiceCollection.AddScoped(Type serviceType, Type implementationType);
  • IServiceCollection.AddScoped(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddScoped<TService>()
  • IServiceCollection.AddScoped<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddScoped<TService, TImplementation>()
  • IServiceCollection.AddScoped<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceCollection.AddSingleton(Type serviceType);
  • IServiceCollection.AddSingleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddSingleton(Type serviceType, Type implementationType);
  • IServiceCollection.AddSingleton(Type serviceType, object implementationInstance);
  • IServiceCollection.AddSingleton<TService>()
  • IServiceCollection.AddSingleton<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddSingleton<TService>(TService implementationInstance)
  • IServiceCollection.AddSingleton<TService, TImplementation>()
  • IServiceCollection.AddSingleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceCollection.AddTransient(Type serviceType);
  • IServiceCollection.AddTransient(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddTransient(Type serviceType, Type implementationType);
  • IServiceCollection.AddTransient<TService>()
  • IServiceCollection.AddTransient<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddTransient<TService, TImplementation>()
  • IServiceCollection.AddTransient<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceProvider.GetService(Type serviceType)
  • IServiceProvider.GetService<T>()
  • IServiceProvider.GetServices(Type serviceType)
  • IServiceProvider.GetServices<T>()

Remarques

Pour utiliser des variantes génériques de méthodes IServiceProvider , vous devez inclure l'espace de noms suivant:

using Microsoft.Extensions.DependencyInjection;

Enregistrez et résolvez manuellement

La méthode privilégiée pour décrire les dépendances consiste à utiliser l’injection de constructeur qui suit le principe de dépendances explicites :

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs (ConfigureServices)

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<ITestService, TestService>();
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(ITestService service)
        {
            int rnd = service.GenerateRandom();
        }
    }
}

Enregistrer les dépendances

Le conteneur intégré est livré avec un ensemble de fonctionnalités intégrées:

Contrôle à vie

public void ConfigureServices(IServiceCollection services)
    {
        // ...
    
        services.AddTransient<ITestService, TestService>();
        // or
        services.AddScoped<ITestService, TestService>();
        // or
        services.AddSingleton<ITestService, TestService>();
        // or
        services.AddSingleton<ITestService>(new TestService());
    }
  • AddTransient : créé chaque fois qu'il est résolu
  • AddScoped : créé une fois par requête
  • AddSingleton : récemment créé une fois par application
  • AddSingleton (instance) : fournit une instance précédemment créée par application

Dépendances énumérables

Il est également possible d'enregistrer des dépendances énumérables:

 services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl1>());
 services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl2>());

Vous pouvez ensuite les consommer comme suit:

public class HomeController : Controller
{
    public HomeController(IEnumerable<ITestService> services)
    {
        // do something with services.
    }
}

Dépendances génériques

Vous pouvez également enregistrer des dépendances génériques:

services.Add(ServiceDescriptor.Singleton(typeof(IKeyValueStore<>), typeof(KeyValueStore<>)));

Et puis consommez-le comme suit:

public class HomeController : Controller
{
    public HomeController(IKeyValueStore<UserSettings> userSettings)
    {
        // do something with services.
    }
}

Récupérer des dépendances sur un contrôleur

Une fois enregistrée, une dépendance peut être extraite en ajoutant des paramètres au constructeur Controller.

// ...
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(ITestService service)
        {
            int rnd = service.GenerateRandom();
        }
    }
}

Injection d'une dépendance dans une action de contrôleur

Une fonction intégrée moins connue est l’injection d’action de contrôleur à l’aide de FromServicesAttribute .

[HttpGet]
public async Task<IActionResult> GetAllAsync([FromServices]IProductService products)
{
     return Ok(await products.GetAllAsync());
}

Une note importante est que le [FromServices] ne peut pas être utilisé comme mécanisme général "Injection de propriété" ou "Injection de méthode"! Il ne peut être utilisé que sur les paramètres de méthode d'une action de contrôleur ou d'un constructeur de contrôleur (dans le constructeur, il est obsolète, car le système ASP.NET Core DI utilise déjà l'injection de constructeur et aucun marqueur supplémentaire n'est requis).

Il ne peut être utilisé nulle part en dehors des contrôleurs, action du contrôleur . Il est également très spécifique à ASP.NET Core MVC et réside dans l'assembly Microsoft.AspNetCore.Mvc.Core .

Citation originale du problème ASP.NET Core MVC GitHub ( Limiter [FromServices] à appliquer uniquement aux paramètres ) concernant cet attribut:

@rynowak:

@Eilon:

Le problème avec les propriétés est qu'il apparaît à beaucoup de personnes qu'il peut être appliqué à n'importe quelle propriété de n'importe quel objet.

D'accord, nous avons eu un certain nombre de problèmes publiés par les utilisateurs avec la confusion sur la façon dont cette fonctionnalité doit être utilisée. Il y a vraiment eu pas mal de commentaires à la fois: "[FromServices] est bizarre et je ne l'aime pas" et "[FromServices] m'a confondu". On se croirait dans un piège et l'équipe répondrait encore à des questions dans des années.

Nous pensons que le scénario le plus précieux pour [FromServices] est sur le paramètre de méthode d'une action pour un service dont vous avez besoin uniquement à cet endroit.

/ cc @ danroth27 - modifications de documents

Pour ceux qui aiment le [FromServices] actuel, je vous recommande fortement d’examiner un système de DI qui peut faire une injection de propriété (Autofac, par exemple).

Remarques:

  • Tous les services enregistrés avec le système d'injection de dépendance .NET Core peuvent être injectés dans l'action d'un contrôleur à l'aide de l'attribut [FromServices] .

  • Le cas le plus pertinent est celui où vous avez besoin d'un service uniquement en une seule méthode et que vous ne voulez pas encombrer le constructeur de votre contrôleur avec une autre dépendance, qui ne sera utilisée qu'une seule fois.

  • Ne peut pas être utilisé en dehors de ASP.NET Core MVC (c'est-à-dire des applications de console .NET Framework ou .NET Core pures), car il réside dans l'assembly Microsoft.AspNetCore.Mvc.Core .

  • Pour l'injection de propriété ou de méthode, vous devez utiliser l'un des conteneurs IoC tiers disponibles (Autofac, Unity, etc.).

Le modèle Options / Options d'injection dans les services

Avec ASP.NET Core, l’équipe Microsoft a également introduit le modèle Options, qui permet d’avoir des options typées robustes et une fois configuré la possibilité d’injecter les options dans vos services.

Nous commençons par une classe typée forte, qui conservera notre configuration.

public class MySettings 
{
    public string Value1 { get; set; }
    public string Value2 { get; set; }
}

Et une entrée dans le appsettings.json .

{
  "mysettings" : {
    "value1": "Hello",
    "value2": "World"
  }
}

Ensuite, nous l'initialisons dans la classe Startup. Il y a deux façons de faire ça

  1. Chargez-le directement depuis la appsettings.json "mysettings" de appsettings.json

    services.Configure<MySettings>(Configuration.GetSection("mysettings"));
    
  2. Le faire manuellement

    services.Configure<MySettings>(new MySettings 
    {
        Value1 = "Hello",
        Value2 = Configuration["mysettings:value2"]
    });
    

    Chaque niveau hiérarchique du appsettings.json est séparé par un : . Puisque value2 est une propriété de l'objet mysettings , on y accède via mysettings:value2 .

Enfin, nous pouvons injecter les options dans nos services, en utilisant l’ IOptions<T>

public class MyService : IMyService
{
    private readonly MySettings settings;

    public MyService(IOptions<MySettings> mysettings) 
    {
        this.settings = mySettings.Value;
    }
}

Remarques

Si IOptions<T> n'est pas configuré au démarrage, l'injection d' IOptions<T> injectera l'instance par défaut de la classe T

Utilisation de services étendus au démarrage de l'application / Amorçage de la base de données

Résoudre les services de portée au démarrage de l'application peut être difficile, car il n'y a pas de demande et donc pas de service de portée.

Résoudre un service de portée au démarrage de l'application via app.ApplicationServices.GetService<AppDbContext>() peut entraîner des problèmes, car il sera créé dans la portée du conteneur global, ce qui en fait un singleton avec la durée de vie de l'application, ce qui peut entraîner à des exceptions comme Cannot access a disposed object in ASP.NET Core when injecting DbContext .

Le modèle suivant résout le problème en créant d'abord une nouvelle étendue, puis en résolvant les services qui lui sont associés, puis, une fois le travail terminé, en éliminant le conteneur de portée.

public Configure(IApplicationBuilder app)
{
    // serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
    using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        var db = serviceScope.ServiceProvider.GetService<AppDbContext>();

        if (await db.Database.EnsureCreatedAsync())
        {
            await SeedDatabase(db);
        }
    }
}

Il s’agit d’une manière semi-officielle de l’équipe principale d’Entity Framework d’ensemencer les données lors du démarrage de l’application et est reflétée dans l’ exemple d’ application MusicStore .

Résoudre les contrôleurs, ViewComponents et TagHelpers via l'injection de dépendances

Par défaut, les contrôleurs, ViewComponents et TagHelpers ne sont pas enregistrés et résolus via le conteneur d'injection de dépendance. Cela se traduit par l'incapacité de faire, c'est-à-dire l'injection de propriété lors de l'utilisation d'un conteneur Inversion of Control (IoC) tiers tel qu'AutoFac.

Pour que les types ASP.NET Core MVC résolvent ces types via IoC, il faut ajouter les enregistrements suivants dans Startup.cs (extrait de l'exemple officiel ControllersFromService sur GitHub)

public void ConfigureServices(IServiceCollection services)
{
    var builder = services
        .AddMvc()
        .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear())
        .AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
        .ConfigureApplicationPartManager(manager =>
        {
            manager.ApplicationParts.Add(new TypesPart(
              typeof(AnotherController),
              typeof(ComponentFromServicesViewComponent),
              typeof(InServicesTagHelper)));

            manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider());
        })
        .AddControllersAsServices()
        .AddViewComponentsAsServices()
        .AddTagHelpersAsServices();

    services.AddTransient<QueryValueService>();
    services.AddTransient<ValueService>();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

Exemple d'injection de dépendance simple (sans Startup.cs)

Cela vous montre comment utiliser le package nuget Microsoft.Extensions.DependencyInjection sans utiliser WebHostBuilder partir de WebHostBuilder (par exemple lorsque vous souhaitez créer quelque chose d'autre qu'une application Web):

internal class Program
{
    public static void Main(string[] args)
    {
        var services = new ServiceCollection(); //Creates the service registry
        services.AddTransient<IMyInterface, MyClass>(); //Add registration of IMyInterface (should create an new instance of MyClass every time)
        var serviceProvider = services.BuildServiceProvider(); //Build dependencies into an IOC container
        var implementation = serviceProvider.GetService<IMyInterface>(); //Gets a dependency

        //serviceProvider.GetService<ServiceDependingOnIMyInterface>(); //Would throw an error since ServiceDependingOnIMyInterface is not registered
        var manualyInstaniate = new ServiceDependingOnIMyInterface(implementation); 

        services.AddTransient<ServiceDependingOnIMyInterface>();
        var spWithService = services.BuildServiceProvider(); //Generaly its bad practise to rebuild the container because its heavey and promotes use of anti-pattern.
        spWithService.GetService<ServiceDependingOnIMyInterface>(); //only now i can resolve
    }
}

interface IMyInterface
{
}

class MyClass : IMyInterface
{
}

class ServiceDependingOnIMyInterface
{
    private readonly IMyInterface _dependency;

    public ServiceDependingOnIMyInterface(IMyInterface dependency)
    {
        _dependency = dependency;
    }
}

Fonctionnement interne de Microsoft.Extensions.DependencyInjection

IServiceCollection

Pour commencer à créer un conteneur IOC avec le package DI nuget de Microsoft, commencez par créer un IServiceCollection . Vous pouvez utiliser la collection déjà fournie: ServiceCollection :

var services = new ServiceCollection();

Cet IServiceCollection n'est rien d'autre qu'une implémentation de: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable

Toutes les méthodes suivantes ne sont que des méthodes d'extension permettant d'ajouter des instances ServiceDescriptor à la liste:

services.AddTransient<Class>(); //add registration that is always recreated
services.AddSingleton<Class>(); // add registration that is only created once and then re-used
services.AddTransient<Abstract, Implementation>(); //specify implementation for interface
services.AddTransient<Interface>(serviceProvider=> new Class(serviceProvider.GetService<IDependency>())); //specify your own resolve function/ factory method.
services.AddMvc(); //extension method by the MVC nuget package, to add a whole bunch of registrations.
// etc..

//when not using an extension method:
services.Add(new ServiceDescriptor(typeof(Interface), typeof(Class)));

IServiceProvider

Le serviceprovider est celui qui "Compile" tous les enregistrements afin qu'ils puissent être utilisés rapidement, cela peut être fait avec services.BuildServiceProvider() qui est fondamentalement un code d'extension pour:

var provider = new ServiceProvider( services, false); //false is if it should validate scopes

En arrière-plan, chaque ServiceDescriptor dans IServiceCollection est compilé dans une méthode de fabrique Func<ServiceProvider, object> où object est le type de retour et correspond à: l'instance créée du type Implementation, Singleton ou votre propre méthode de fabrique définie.

Ces enregistrements sont ajoutés à la ServiceTable qui est essentiellement un ConcurrentDictionary la clé étant ServiceType et la valeur de la méthode Factory définie ci-dessus.

Résultat

Nous avons maintenant un objet ConcurrentDictionary<Type, Func<ServiceProvider, object>> que nous pouvons utiliser simultanément pour demander de créer des services pour nous. Pour montrer un exemple de base de ce à quoi cela aurait pu ressembler.

  var serviceProvider = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
  var factoryMethod = serviceProvider[typeof(MyService)];
  var myServiceInstance = factoryMethod(serviceProvider)

Ce n'est pas comme ça que ça marche!

Ce ConcurrentDictionary est une propriété du ServiceTable qui est une propriété du ServiceProvider



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow