Ricerca…


introduzione

Il core Aspnet è costruito con Dependency Injection come uno dei suoi concetti chiave. Introduce un'astrazione di contenitore conforme in modo che sia possibile sostituire quello incorporato con un contenitore di terze parti di propria scelta.

Sintassi

  • 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>()

Osservazioni

Per utilizzare varianti generiche dei metodi IServiceProvider devi includere il seguente spazio dei nomi:

using Microsoft.Extensions.DependencyInjection;

Registrati e risolvi manualmente

Il modo migliore di descrivere le dipendenze è usando l'iniezione del costruttore che segue il principio delle dipendenze esplicite :

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();
        }
    }
}

Registrare dipendenze

Il contenitore incorporato include una serie di funzionalità integrate:

Controllo a vita

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 : creato ogni volta che viene risolto
  • AddScoped : creato una volta per richiesta
  • AddSingleton : creato in modo semplice una volta per applicazione
  • AddSingleton (istanza) : fornisce un'istanza creata in precedenza per ogni applicazione

Dipendenze enumerabili

È anche possibile registrare dipendenze enumerabili:

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

Puoi quindi consumarli come segue:

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

Dipendenze generiche

Puoi anche registrare le dipendenze generiche:

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

E poi consumarlo come segue:

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

Recupera dipendenze su un controller

Una volta registrata, una dipendenza può essere recuperata aggiungendo parametri al costruttore di Controller.

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

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

Iniezione di una dipendenza in un'azione del controllore

Una funzione incorporata meno conosciuta è l'iniezione di Controller Action che utilizza FromServicesAttribute .

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

Una nota importante è che i [FromServices] non possono essere utilizzati come meccanismo generale "Property Injection" o "Method injection"! Può essere utilizzato solo sui parametri del metodo di un'azione del controllore o del costruttore del controllore (nel costruttore è tuttavia obsoleto, poiché il sistema DI di base di ASP.NET utilizza già l'iniezione del costruttore e non sono richiesti marker aggiuntivi).

Non può essere utilizzato ovunque al di fuori di un controller, azione del controller . Inoltre è molto specifico per ASP.NET Core MVC e risiede nell'assembly Microsoft.AspNetCore.Mvc.Core .

Preventivo originale dal problema GitHub di ASP.NET Core MVC ( Limite [FromServices] da applicare solo ai parametri ) relativo a questo attributo:

@rynowak:

@Eilon:

Il problema con le proprietà è che sembra a molte persone che possa essere applicato a qualsiasi proprietà di qualsiasi oggetto.

D'accordo, abbiamo avuto un numero di problemi pubblicati dagli utenti con confusione su come questa funzione dovrebbe essere utilizzata. C'è stata una grande quantità di feedback sia del tipo "[FromServices] è strano e non mi piace" e "[FromServices] mi ha confuso". Sembra una trappola, e qualcosa che il team avrebbe comunque risposto alle domande tra anni.

Riteniamo che lo scenario più prezioso per [FromServices] sia sul parametro method su un'azione per un servizio di cui hai bisogno solo in quella posizione.

/ cc @ danroth27 - modifiche di documenti

Per chiunque sia innamorato dell'attuale [FromServices], consiglio vivamente di cercare in un sistema DI che possa fare l'iniezione di proprietà (Autofac, ad esempio).

Gli appunti:

  • Qualsiasi servizio registrato con il sistema .NET Core Dependency Injection può essere iniettato all'interno dell'azione di un controller utilizzando l'attributo [FromServices] .

  • Il caso più rilevante è quando hai bisogno di un servizio solo in un singolo metodo di azione e non vuoi ingombrare il costruttore del controllore con un'altra dipendenza, che sarà usata una sola volta.

  • Non può essere utilizzato al di fuori di ASP.NET Core MVC (ovvero puro .NET Framework o applicazioni della console .NET Core), poiché risiede nell'assembly Microsoft.AspNetCore.Mvc.Core .

  • Per la proprietà o il metodo di iniezione è necessario utilizzare uno dei contenitori IoC di terze parti disponibili (Autofac, Unity, ecc.).

Il modello di opzioni / opzioni di iniezione nei servizi

Con ASP.NET Core, il team Microsoft ha anche introdotto il modello Options, che consente di disporre di potenti opzioni tipizzate e una volta configurata la possibilità di iniettare le opzioni nei servizi.

Per prima cosa iniziamo con una classe tipizzata forte, che manterrà la nostra configurazione.

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

E una voce nel appsettings.json .

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

Successivamente inizializziamo nella classe Startup. Ci sono due modi per farlo

  1. appsettings.json direttamente dalla sezione appsettings.json "mysettings"

    services.Configure<MySettings>(Configuration.GetSection("mysettings"));
    
  2. Fallo manualmente

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

    Ogni livello gerarchico di appsettings.json è separato da un : Poiché value2 è una proprietà dell'oggetto mysettings , accediamo tramite mysettings:value2 .

Infine possiamo iniettare le opzioni nei nostri servizi, usando l' IOptions<T>

public class MyService : IMyService
{
    private readonly MySettings settings;

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

Osservazioni

Se le IOptions<T> non sono configurate durante l'avvio, l'iniezione di IOptions<T> inietterà l'istanza predefinita della classe T

Utilizzo dei servizi di ambito durante l'avvio dell'applicazione / Seeding del database

La risoluzione dei servizi con ambito durante l'avvio dell'applicazione può essere difficile, poiché non vi è alcuna richiesta e quindi nessun servizio con ambito.

La risoluzione di un servizio con ambito durante l'avvio dell'applicazione tramite app.ApplicationServices.GetService<AppDbContext>() può causare problemi, poiché verrà creata nell'ambito del contenitore globale, rendendola effettivamente un singleton con il ciclo di vita dell'applicazione, che può portare alle eccezioni come Cannot access a disposed object in ASP.NET Core when injecting DbContext .

Il seguente schema risolve il problema creando innanzitutto un nuovo ambito e quindi risolvendo i servizi di ambito da esso, quindi, una volta terminato il lavoro, lo smaltimento del contenitore con ambito.

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);
        }
    }
}

Questo è un modo semi-ufficiale del core team di Entity Framework per seminare i dati durante l'avvio dell'applicazione e si riflette nell'applicazione di esempio MusicStore .

Risolvi controller, ViewComponents e TagHelpers tramite Iniezione dipendenza

Per impostazione predefinita, Controller, ViewComponents e TagHelpers non sono registrati e risolti tramite il contenitore di dipendenze dell'iniezione. Ciò si traduce nell'incapacità di fare l'iniezione di proprietà quando si utilizza un contenitore di Inversion of Control (IoC) di terze parti come AutoFac.

Per fare in modo che ASP.NET Core MVC risolva questi tipi anche tramite IoC, è necessario aggiungere le seguenti registrazioni in Startup.cs (prelevate dall'esempio ufficiale di ControllersFromService su 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>();
}

Esempio di Plain Dependency Injection (senza Startup.cs)

Questo ti mostra come usare il pacchetto Microsoft.Extensions.DependencyInjection nuget senza l'uso di WebHostBuilder da kestrel (ad es. Quando vuoi costruire qualcos'altro di una webApp):

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;
    }
}

Funzionamento interno di Microsoft.Extensions.DependencyInjection

IServiceCollection

Per iniziare a costruire un container IOC con il pacchetto DI nuget di Microsoft, inizierai con la creazione di un servizio IServiceCollection . Puoi utilizzare la raccolta già fornita: ServiceCollection :

var services = new ServiceCollection();

Questo IServiceCollection non è altro che un'implementazione di: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable

Tutti i seguenti metodi sono solo metodi di estensione per aggiungere istanze ServiceDescriptor all'elenco:

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

Il provider di servizi è quello che "Compila" tutte le registrazioni in modo che possano essere utilizzate rapidamente, ciò può essere fatto con services.BuildServiceProvider() che è fondamentalmente un mehtod di estensione per:

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

Dietro le quinte ogni ServiceDescriptor in IServiceCollection viene compilato con un metodo factory Func<ServiceProvider, object> dove object è il tipo restituito ed è: l'istanza creata del tipo Implementation, Singleton o il proprio metodo factory definito.

Queste registrazioni vengono aggiunte al ServiceTable che è fondamentalmente un ConcurrentDictionary con la chiave come ServiceType e il valore del metodo Factory definito sopra.

Risultato

Ora disponiamo di un ConcurrentDictionary<Type, Func<ServiceProvider, object>> che possiamo usare contemporaneamente per chiedere di creare servizi per noi. Per mostrare un esempio di base di come questo avrebbe potuto sembrare.

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

Non è così che funziona!

Questo ConcurrentDictionary è una proprietà del ServiceTable che è una proprietà del ServiceProvider



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow