asp.net-core
Abhängigkeitsspritze
Suche…
Einführung
Der Aspnet-Kern wird mit Abhängigkeitsinjektion als einem der wichtigsten Kernkonzepte erstellt. Es führt eine konforme Containerabstraktion ein, sodass Sie den integrierten Container durch einen Container eines anderen Herstellers ersetzen können.
Syntax
-
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>()
Bemerkungen
Um generische Varianten von IServiceProvider
Methoden verwenden zu können, müssen Sie den folgenden Namespace IServiceProvider
:
using Microsoft.Extensions.DependencyInjection;
Registrieren und manuell auflösen
Die bevorzugte Art, Abhängigkeiten zu beschreiben, ist die Verwendung der Konstruktorinjektion, die dem Prinzip der expliziten Abhängigkeiten folgt:
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();
}
}
}
Abhängigkeiten registrieren
Der eingebaute Container verfügt über eine Reihe von integrierten Funktionen:
Lebensdauerkontrolle
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 : Wird bei jeder Auflösung erstellt
- AddScoped : Einmal pro Anfrage erstellt
- AddSingleton : Einmal pro Anwendung faul erstellt
- AddSingleton (Instanz) : Stellt eine zuvor erstellte Instanz pro Anwendung bereit
Aufzählbare Abhängigkeiten
Es ist auch möglich, zahlbare Abhängigkeiten zu registrieren:
services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl1>());
services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl2>());
Sie können sie dann wie folgt verzehren:
public class HomeController : Controller
{
public HomeController(IEnumerable<ITestService> services)
{
// do something with services.
}
}
Generische Abhängigkeiten
Sie können auch generische Abhängigkeiten registrieren:
services.Add(ServiceDescriptor.Singleton(typeof(IKeyValueStore<>), typeof(KeyValueStore<>)));
Und verbrauchen Sie es dann wie folgt:
public class HomeController : Controller
{
public HomeController(IKeyValueStore<UserSettings> userSettings)
{
// do something with services.
}
}
Abhängigkeiten von einem Controller abrufen
Nach der Registrierung kann eine Abhängigkeit durch Hinzufügen von Parametern im Controller-Konstruktor abgerufen werden.
// ...
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Core.Controllers
{
public class HomeController : Controller
{
public HomeController(ITestService service)
{
int rnd = service.GenerateRandom();
}
}
}
Injizieren einer Abhängigkeit in eine Controller-Aktion
Eine weniger bekannte integrierte Funktion ist die Injektion von Controller-Aktionen unter Verwendung des FromServicesAttribute
.
[HttpGet]
public async Task<IActionResult> GetAllAsync([FromServices]IProductService products)
{
return Ok(await products.GetAllAsync());
}
Ein wichtiger Hinweis ist, dass die [FromServices]
nicht als allgemeiner "Property Injection" - oder "Method Injection" -Mechanismus verwendet werden können! Es kann nur für Methodenparameter einer Controller-Aktion oder eines Controller-Konstruktors verwendet werden (im Konstruktor ist es jedoch veraltet, da das ASP.NET Core DI-System bereits die Konstruktorinjektion verwendet und keine zusätzlichen Marker erforderlich sind).
Es kann an keiner Stelle außerhalb eines Controllers verwendet werden . Es ist auch sehr spezifisch für ASP.NET Core MVC und befindet sich in der Microsoft.AspNetCore.Mvc.Core
Assembly.
Ursprüngliches Zitat aus dem ASP.NET Core MVC-GitHub-Problem ( Limit [FromServices], das nur für Parameter gilt ) bezüglich dieses Attributs:
@rynowak:
@Eilon:
Das Problem bei Eigenschaften ist, dass es vielen Leuten scheint, dass es auf jede Eigenschaft eines Objekts angewendet werden kann.
Es wurde vereinbart, dass eine Reihe von Problemen von Benutzern mit Verwirrung bezüglich der Verwendung dieser Funktion gepostet wurden. Es gab wirklich eine ziemlich große Menge an Feedback. Beide Arten "[FromServices] sind komisch und ich mag es nicht" und "[FromServices] hat mich verwirrt". Es fühlt sich an wie eine Falle und etwas, dass das Team noch Fragen über Jahre beantworten würde.
Wir halten das wertvollste Szenario für [FromServices] für einen Parameter einer Aktion für einen Dienst, den Sie nur an dieser Stelle benötigen.
/ cc @ danroth27 - Änderungen werden vorgenommen
Jedem, der sich in die aktuellen [FromServices] -Liebhaber verliebt, würde ich dringend nach einem DI-System suchen, das die Eigenschaftsinjektion (beispielsweise Autofac) durchführt.
Anmerkungen:
Alle Dienste, die im .NET Core Dependency Injection-System registriert sind, können mithilfe des Attributs
[FromServices]
die Aktion eines Controllers[FromServices]
werden.Der relevanteste Fall ist, wenn Sie einen Service nur in einer einzigen Aktionsmethode benötigen und den Konstruktor Ihres Controllers nicht mit einer anderen Abhängigkeit überladen möchten, die nur einmal verwendet wird.
Kann nicht außerhalb von ASP.NET Core MVC (dh reinen .NET Framework- oder .NET Core-Konsolenanwendungen) verwendet werden, da es sich in der Assembly
Microsoft.AspNetCore.Mvc.Core
.Für die Eigenschafts- oder Methodeninjektion müssen Sie einen der verfügbaren IoC-Container von Drittanbietern verwenden (Autofac, Unity usw.).
Die Optionen Muster / Einspritzen von Optionen in Dienste
Mit ASP.NET Core hat das Microsoft-Team außerdem das Optionsmuster eingeführt, das es ermöglicht, stark typisierte Optionen zu haben und die Möglichkeit zu konfigurieren, die Optionen in Ihre Services einzufügen.
Zuerst beginnen wir mit einer stark typisierten Klasse, die unsere Konfiguration enthält.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
Und einen Eintrag in der appsettings.json
.
{
"mysettings" : {
"value1": "Hello",
"value2": "World"
}
}
Als nächstes initialisieren wir es in der Startup-Klasse. Dafür gibt es zwei Möglichkeiten
Laden Sie es direkt aus dem
appsettings.json
Abschnitt "mysettings"services.Configure<MySettings>(Configuration.GetSection("mysettings"));
Mach es manuell
services.Configure<MySettings>(new MySettings { Value1 = "Hello", Value2 = Configuration["mysettings:value2"] });
Jede Hierarchieebene von
appsettings.json
ist durchappsettings.json
getrennt:
Davalue2
eine Eigenschaft der istmysettings
Objekt greifen wir es übermysettings:value2
.
Schließlich können wir die Optionen über die IOptions<T>
-Schnittstelle in unsere Dienste IOptions<T>
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
Bemerkungen
Wenn die IOptions<T>
während des Starts nicht konfiguriert ist, wird durch das IOptions<T>
die Standardinstanz der T
Klasse IOptions<T>
.
Verwenden der Bereichsdienste während des Anwendungsstarts / Database Seeding
Das Auflösen von Bereichsdiensten während des Anwendungsstarts kann schwierig sein, da keine Anforderung und daher kein Bereichsdienst vorhanden ist.
Das Auflösen eines Bereichsdienstes während des Anwendungsstarts über app.ApplicationServices.GetService<AppDbContext>()
kann zu Problemen führen, da er im Gültigkeitsbereich des globalen Containers erstellt wird, wodurch er mit der Lebensdauer der Anwendung zu einem Singleton wird Ausnahmen wie Der Cannot access a disposed object in ASP.NET Core when injecting DbContext
.
Das folgende Muster behebt das Problem, indem zuerst ein neuer Bereich erstellt und anschließend die Scope-Services aufgelöst werden. Wenn die Arbeit abgeschlossen ist, wird der Scope-Container entsorgt.
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);
}
}
}
Dies ist eine halboffizielle Methode des Entity Framework-Kernteams, Daten während des Anwendungsstarts zu säen. Dies wird in der MusicStore-Beispielanwendung angezeigt .
Lösen Sie Controller, ViewComponents und TagHelpers über Abhängigkeitseinspritzung auf
Standardmäßig werden Controller, ViewComponents und TagHelpers nicht über den Abhängigkeitsinjektionscontainer registriert und aufgelöst. Dies führt dazu, dass keine Eigenschaftsinjektion durchgeführt werden kann, wenn ein Inversion of Control-Container (IoC) von Drittanbietern wie AutoFac verwendet wird.
Damit ASP.NET Core MVC diese Typen auch über IoC auflösen kann, müssen die folgenden Registrierungen in Startup.cs
hinzugefügt werden (aus dem offiziellen ControllersFromService-Beispiel auf 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>();
}
Beispiel für eine einfache Abhängigkeitsinjektion (ohne Startup.cs)
Hier erfahren Sie, wie Sie das Nuget-Paket Microsoft.Extensions.DependencyInjection ohne die Verwendung des WebHostBuilder
von Kestrel verwenden (z. B. wenn Sie etwas anderes als eine WebApp erstellen möchten):
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;
}
}
Innere Funktionen von Microsoft.Extensions.DependencyInjection
IServiceCollection
Um einen IOC-Container mit dem DI-Nuget-Paket von Microsoft zu erstellen, beginnen Sie mit dem Erstellen einer IServiceCollection
. Sie können die bereits bereitgestellte Collection verwenden: ServiceCollection
:
var services = new ServiceCollection();
Diese IServiceCollection
ist nichts weiter als eine Implementierung von: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
Alle folgenden Methoden sind nur Erweiterungsmethoden, um der Liste ServiceDescriptor
Instanzen hinzuzufügen:
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
Der Dienstanbieter ist derjenige, der alle Registrierungen "kompiliert", so dass sie schnell verwendet werden können. Dies kann mit services.BuildServiceProvider()
Dies ist im Wesentlichen eine Erweiterung für:
var provider = new ServiceProvider( services, false); //false is if it should validate scopes
Hinter den Kulissen wird jeder ServiceDescriptor
in der IServiceCollection
zu einer Factory-Methode Func<ServiceProvider, object>
kompiliert, wobei object der Rückgabetyp ist und die erzeugte Instanz des Implementierungstyps, das Singleton oder Ihre eigene Factory-Methode ist.
Diese Registrierungen werden der ServiceTable
hinzugefügt, bei der es sich im Wesentlichen um ein ConcurrentDictionary
wobei der Schlüssel der ServiceType
und der oben definierte Wert die Factory-Methode ist.
Ergebnis
Jetzt haben wir ein ConcurrentDictionary<Type, Func<ServiceProvider, object>>
dem wir gleichzeitig die Erstellung von Services für uns ConcurrentDictionary<Type, Func<ServiceProvider, object>>
können. Um ein grundlegendes Beispiel zu zeigen, wie dies aussehen könnte.
var serviceProvider = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
var factoryMethod = serviceProvider[typeof(MyService)];
var myServiceInstance = factoryMethod(serviceProvider)
So funktioniert es nicht!
Dieses ConcurrentDictionary
ist eine Eigenschaft der ServiceTable
die eine Eigenschaft des ServiceProvider