asp.net-core
Beroende på injektion
Sök…
Introduktion
Aspnet-kärnan är byggd med Dependency Injection som ett av dess viktigaste kärnkoncept. Den introducerar en överensstämmande behållarabstraktion så att du kan byta ut den inbyggda med en tredje parts behållare som du väljer.
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>()
Anmärkningar
För att använda generiska varianter av IServiceProvider
metoder måste du inkludera följande namnutrymme:
using Microsoft.Extensions.DependencyInjection;
Registrera och lösa manuellt
Det föredragna sättet att beskriva beroenden är att använda konstruktionsinjektion som följer Explicit Dependencies Principle :
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();
}
}
}
Registrera beroenden
Builtin container levereras med en uppsättning inbyggda funktioner:
Livstidskontroll
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 : Skapas varje gång det löses
- AddScoped : Skapad en gång per förfrågan
- AddSingleton : Lazily skapas en gång per applikation
- AddSingleton (instans) : Ger en tidigare skapad instans per applikation
Otaliga beroenden
Det är också möjligt att registrera ett antal beroende:
services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl1>());
services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl2>());
Du kan sedan konsumera dem enligt följande:
public class HomeController : Controller
{
public HomeController(IEnumerable<ITestService> services)
{
// do something with services.
}
}
Generiska beroenden
Du kan också registrera generiska beroenden:
services.Add(ServiceDescriptor.Singleton(typeof(IKeyValueStore<>), typeof(KeyValueStore<>)));
Och sedan konsumera det enligt följande:
public class HomeController : Controller
{
public HomeController(IKeyValueStore<UserSettings> userSettings)
{
// do something with services.
}
}
Hämta beroenden på en kontroller
När du har registrerat ett beroende kan hämtas genom att lägga till parametrar på Controller-konstruktorn.
// ...
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Core.Controllers
{
public class HomeController : Controller
{
public HomeController(ITestService service)
{
int rnd = service.GenerateRandom();
}
}
}
Injicera ett beroende i en kontrollåtgärd
En mindre känd inbyggd funktion är injektion av Controller Action med hjälp av FromServicesAttribute
.
[HttpGet]
public async Task<IActionResult> GetAllAsync([FromServices]IProductService products)
{
return Ok(await products.GetAllAsync());
}
En viktig anmärkning är att [FromServices]
inte kan användas som generell mekanism för "injektion av egendom" eller "metodinjektion"! Det kan bara användas på metodparametrar för en kontrolleråtgärd eller styrenhetskonstruktör (i konstruktören är det dock föråldrat, eftersom ASP.NET Core DI-system redan använder konstruktionsinjektion och det behövs inga extra markörer).
Det kan inte användas någonstans utanför en kontroller, kontrolleråtgärd . Det är också mycket specifikt för ASP.NET Core MVC och finns i Microsoft.AspNetCore.Mvc.Core
enheten.
Ursprungligt offert från ASP.NET Core MVC GitHub-frågan ( Begränsa [FromServices] att endast gälla för parametrar ) angående detta attribut:
@rynowak:
@Eilon:
Problemet med egenskaper är att det verkar för många att det kan tillämpas på alla egenskaper hos något objekt.
Håller med om, vi har haft ett antal problem som har skickats av användare med förvirring kring hur den här funktionen ska användas. Det har verkligen varit en ganska stor mängd återkoppling av båda typerna "[FromServices] är konstigt och jag gillar det inte" och "[FromServices] har förvirrat mig". Det känns som en fälla, och något som teamet fortfarande skulle svara på frågor om år från och med nu.
Vi känner att det mest värdefulla scenariot för [FromServices] är på metodparametern till en åtgärd för en tjänst som du bara behöver på den ena platsen.
/ cc @ danroth27 - dokumentändringar
För alla som är förälskade i det nuvarande [FromServices], rekommenderar jag starkt att du tittar på ett DI-system som kan göra injektion av egendom (till exempel Autofac).
Anmärkningar:
Alla tjänster som är registrerade med .NET Core Dependency Injection-systemet kan injiceras i en kontrollers handling med attributet
[FromServices]
.Det mest relevanta fallet är när du bara behöver en tjänst i en enda åtgärdsmetod och inte vill röran din controllers konstruktör med ett annat beroende, som bara kommer att användas en gång.
Kan inte användas utanför ASP.NET Core MVC (dvs. ren. NET Framework eller. NET Core-konsolapplikationer), eftersom den finns i
Microsoft.AspNetCore.Mvc.Core
montering.För egendom eller metodinjektion måste du använda en av tredje part IoC-containrar som finns tillgängliga (Autofac, Unity, etc.).
Alternativmönstret / Injicera alternativ i tjänster
Med ASP.NET Core introducerade Microsoft-teamet också alternativmönstret, som gör det möjligt att ha starkt typade alternativ och en gång har konfigurerat möjligheten att injicera alternativen i dina tjänster.
Först börjar vi med en stark typklass, som kommer att hålla vår konfiguration.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
Och en post i appsettings.json
.
{
"mysettings" : {
"value1": "Hello",
"value2": "World"
}
}
Därefter initierar vi det i Start-klassen. Det finns två sätt att göra detta
Ladda den direkt från
appsettings.json
"mysettings"services.Configure<MySettings>(Configuration.GetSection("mysettings"));
Gör det manuellt
services.Configure<MySettings>(new MySettings { Value1 = "Hello", Value2 = Configuration["mysettings:value2"] });
Varje hierarkinivå i
appsettings.json
är åtskilda av en:
. Eftersomvalue2
är en egenskap hosmysettings
objektet, får vi åtkomst till det viamysettings:value2
.
Slutligen kan vi injicera alternativen i våra tjänster med IOptions<T>
gränssnitt
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
Anmärkningar
Om IOptions<T>
inte är konfigurerad under uppstarten injicerar IOptions<T>
standardinstansen för T
klass.
Använda scoped-tjänster under applikationsstart / databasfrö
Att lösa scopedtjänster under applikationens start kan vara svårt, eftersom det inte finns någon begäran och därmed ingen scopedtjänst.
Att lösa en scoped-tjänst under applikationsstart via app.ApplicationServices.GetService<AppDbContext>()
kan orsaka problem eftersom det kommer att skapas inom ramen för den globala behållaren, vilket effektivt gör det till en singleton med applikationens livslängd, vilket kan leda till till undantag som inte Cannot access a disposed object in ASP.NET Core when injecting DbContext
.
Följande mönster löser problemet genom att först skapa ett nytt omfång och sedan lösa scopedtjänsterna från det, sedan när arbetet är gjort, bortskaffa scopedcontainern.
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);
}
}
}
Detta är ett semi-officiellt sätt för Entity Framework-kärngruppen att såddata under applikationsstart och återspeglas i exempelvis applikationen MusicStore .
Lös styrenheter, ViewComponents och TagHelpers via beroende injektion
Som standard är Controllers, ViewComponents och TagHelpers inte registrerade och lösade via behållaren för beroendeinjektion. Detta resulterar i oförmågan att göra egendomsinjektion när du använder en tredje part Inversion of Control (IoC) behållare som AutoFac.
För att få ASP.NET Core MVC att lösa dessa typer via IoC också, måste man lägga till följande registreringar i Startup.cs
(hämtat från det officiella ControllersFromService-exemplet på 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>();
}
Exempel på injektion av vanligt beroende (utan start.cs)
Detta visar hur du använder Microsoft.Extensions.DependencyInjection- nuget-paketet utan att använda WebHostBuilder
från kestrel (t.ex. när du vill bygga något annat än en 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;
}
}
Inre funktioner i Microsoft.Extensions.DependencyInjection
IServiceCollection
För att börja bygga en IOC-behållare med Microsofts DI nuget-paket börjar du med att skapa en IServiceCollection
. Du kan använda den redan angivna samlingen: ServiceCollection
:
var services = new ServiceCollection();
Denna IServiceCollection
är inget annat än en implementering av: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
Alla följande metoder är endast förlängningsmetoder för att lägga till ServiceDescriptor
instanser i listan:
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
Serviceprovider är den som "sammanställer" alla registreringar så att de kan användas snabbt, detta kan göras med services.BuildServiceProvider()
var provider = new ServiceProvider( services, false); //false is if it should validate scopes
Bakom kulisserna sammanställs varje ServiceDescriptor
i IServiceCollection
till en fabriksmetod Func<ServiceProvider, object>
där objekt är returtyp och är: den skapade instansen av Implementationstyp, Singleton eller din egen definierade fabriksmetod.
Dessa registreringar läggs till i ServiceTable
som i princip är en ConcurrentDictionary
med nyckeln ServiceType
och värdet Factory-metoden definierad ovan.
Resultat
Nu har vi en ConcurrentDictionary<Type, Func<ServiceProvider, object>>
som vi kan använda samtidigt för att be om att skapa tjänster för oss. För att visa ett grundläggande exempel på hur detta kunde ha sett ut.
var serviceProvider = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
var factoryMethod = serviceProvider[typeof(MyService)];
var myServiceInstance = factoryMethod(serviceProvider)
Så här fungerar det inte!
Denna ConcurrentDictionary
är en egenskap hos ServiceTable
som tillhör ServiceProvider