asp.net-core
Iniezione di dipendenza
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
appsettings.json
direttamente dalla sezioneappsettings.json
"mysettings"services.Configure<MySettings>(Configuration.GetSection("mysettings"));
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'oggettomysettings
, accediamo tramitemysettings: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