Beroendeinmatning i ASP.NET Core
Not
Det här är inte den senaste versionen av den här artikeln. För den aktuella versionen se .NET 9-versionen av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella versionen, se .NET 9-versionen av den här artikeln.
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Av Kirk Larkin, Steve Smithoch Brandon Dahler
ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå Inversion av kontroll (IoC) mellan klasser och deras beroenden.
Vägledning för Blazor DI, som lägger till eller ersätter vägledningen i den här artikeln, finns i ASP.NET Core Blazor beroendeinjektion.
Information som är specifik för beroendeinmatning inom MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.
Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.
Information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.
Den här artikeln innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.
Visa eller ladda ned exempelkod (hur du laddar ned)
Översikt över beroendeinmatning
Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency
-klass med en WriteMessage
metod som andra klasser är beroende av:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
En klass kan skapa en instans av klassen MyDependency
för att använda sin WriteMessage
-metod. I följande exempel är klassen MyDependency
ett beroende av klassen IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Klassen skapar och är direkt beroende av klassen MyDependency
. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:
- Om du vill ersätta
MyDependency
med en annan implementering måste klassenIndexModel
ändras. - Om
MyDependency
har beroenden måste de också konfigureras avIndexModel
-klassen. I ett stort projekt med flera klasser beroende påMyDependency
blir konfigurationskoden utspridd över appen. - Den här implementeringen är svår att enhetstesta.
Beroendeinjektion åtgärdar dessa problem genom:
- Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
- Registrering av beroende i en tjänstcontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens
Program.cs
-fil. - Inmatning av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.
I exempelappendefinierar IMyDependency
-gränssnittet WriteMessage
-metoden:
public interface IMyDependency
{
void WriteMessage(string message);
}
Det här gränssnittet implementeras av en konkret typ, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Exempelappen registrerar IMyDependency
-tjänsten med den konkreta typen MyDependency
. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran.
Tjänstlivslängder beskrivs senare i den här artikeln.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
I exempelappen begärs IMyDependency
-tjänsten och används för att anropa metoden WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Genom att använda DI-mönstret, kontrollern eller sidan Razor
- Använder inte konkret typen
MyDependency
, utan endast det gränssnittIMyDependency
som den implementerar. Det gör det enkelt att ändra implementeringen utan att ändra kontrollanten eller Razor sidan. - Skapar inte någon instans av
MyDependency
, den skapas av DI-containern.
Implementeringen av IMyDependency
-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Den uppdaterade Program.cs
registrerar den nya IMyDependency
implementeringen:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
beror på ILogger<TCategoryName>, som begärs i konstruktorn.
ILogger<TCategoryName>
är en tjänst som tillhandahålls av ramverket.
Det är inte ovanligt att använda beroendeinmatning på ett kedjat sätt. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.
Containern löser ILogger<TCategoryName>
genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.
I beroendeinjiceringsterminologin är en tjänst:
- Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel
IMyDependency
-tjänsten. - Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.
Ramverket ger ett robust loggningssystem. De IMyDependency
implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Föregående kod fungerar korrekt utan att ändra något i Program.cs
eftersom loggning tillhandahålls av ramverket.
Registrera grupper av tjänster med tilläggsmetoder
ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME}
tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. Metoden AddControllers tilläggsmetod registrerar till exempel de tjänster som krävs för MVC-kontrollanter.
Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och AddDefaultIdentity:
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Överväg följande som registrerar tjänster och konfigurerar alternativ:
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
var app = builder.Build();
Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
De återstående tjänsterna registreras i en liknande klass. Följande kod använder de nya tilläggsmetoderna för att registrera tjänsterna:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Obs! Varje services.Add{GROUP_NAME}
tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver.
Tjänstlivslängd
Se tjänstelivslängder i beroendeinjektion i .NET
Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:
- Mata in tjänsten i mellanprogrammets
Invoke
- ellerInvokeAsync
-metod. Om du använder konstruktorinmatning utlöser ett körningsundantag eftersom det tvingar den begränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registreringsalternativ visar tillvägagångssättetInvokeAsync
. - Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets konstruktor.
Mer information finns i Skriv anpassad ASP.NET Core-mellanvara.
Metoder för tjänstregistrering
Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET
Det är vanligt att använda flera implementeringar när modelleringstyper för att testa.
Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.
Någon av dessa tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton
två gånger med IMyDependency
som tjänsttyp. Det andra anropet till AddSingleton
åsidosätter det föregående när det löses som IMyDependency
och adderas till det föregående när flera tjänster löses via IEnumerable<IMyDependency>
. Tjänsterna visas i den ordning de registrerades när de löstes via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Nyckelade tjänster
Termen nyckelade tjänster refererar till en mekanism för att registrera och hämta DI-tjänster (Dependency Injection) med hjälp av nycklar. En tjänst associeras med en nyckel genom att anropa AddKeyedSingleton (eller AddKeyedScoped
eller AddKeyedTransient
) för att registrera den. Få åtkomst till en registrerad tjänst genom att ange nyckeln med attributet [FromKeyedServices]
. Följande kod visar hur du använder nyckelade tjänster:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
Nyckelade tjänster i Mellanprogram
Mellanprogram stöder Keyed-tjänster i både konstruktorn och metoden Invoke
/InvokeAsync
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");
var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();
internal class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next,
[FromKeyedServices("test")] MySingletonClass service)
{
_next = next;
}
public Task Invoke(HttpContext context,
[FromKeyedServices("test2")]
MyScopedClass scopedService) => _next(context);
}
Mer information om hur du skapar Mellanprogram finns i Skriva anpassade ASP.NET Core-mellanprogram
Konstruktorinmatningsbeteende
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Entity Framework-kontexter
Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livslängd anger du livslängden med hjälp av en AddDbContext överlagring. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.
Alternativ för livslängd och registrering
För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId
. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Följande Operation
-klass implementerar alla föregående gränssnitt. Konstruktorn Operation
genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Följande kod skapar flera registreringar av klassen Operation
enligt de namngivna livslängderna:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Exempelappen visar objektlivslängder både inom och mellan begäranden.
IndexModel
och mellanprogram begär varje typ av IOperation
typ och loggar OperationId
för var och en:
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
På samma sätt som i IndexModel
löser mellanprogrammet samma tjänster:
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Scopade och tillfälliga tjänster måste lösas i InvokeAsync
-metoden.
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Loggningsutdata visar:
-
Tillfälliga objekt är alltid olika. Det tillfälliga
OperationId
värdet skiljer sig iIndexModel
och i mellanprogrammet. - Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
- Singleton- objekt är desamma för varje begäran.
Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Lösa en tjänst vid appstart
Följande kod visar hur du löser en begränsad tjänst under en begränsad tid när appen startar:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
app.MapGet("/", () => "Hello World!");
app.Run();
Omfångsverifiering
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Mer information finns i Omfångsverifiering.
Begär tjänster
Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.
Ramverket skapar ett omfång per begäran och RequestServices
exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.
Notis
Föredra att begära beroenden som konstruktorparametrar före att lösa tjänster från RequestServices
. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.
Utforma tjänster för beroendeinmatning
När du utformar tjänster för beroendeinjektion:
- Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
- Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
- Gör tjänsterna små, välräknade och enkelt testade.
Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.
Avyttring av tjänster
Containern anropar Dispose för typerna IDisposable den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton, tas singletonen automatiskt bort av containern.
I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Tjänster som inte har skapats av tjänstcontainern
Överväg följande kod:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
I föregående kod:
- Tjänstinstanserna skapas inte av tjänstcontainern.
- Ramverket tar inte bort tjänsterna automatiskt.
- Utvecklaren ansvarar för att ta bort tjänsterna.
IDisposable-vägledning för transienta och gemensamma instanser
Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET
Ersättning av standardtjänstcontainer
Se Standardtjänstcontainer-byte i Beroendeinjektion i .NET
Rekommendationer
Se rekommendationer i beroendeinmatning i .NET
Undvik att använda mönstret för att lokalisera tjänster . Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:
felaktig:
Rätt:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
En annan service locator-variant att undvika är att injektera en fabrik som löser beroenden vid körning. Båda dessa metoder blandar inversion av kontroll strategier.
Undvik statisk åtkomst till
HttpContext
(till exempel IHttpContextAccessor.HttpContext).
DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med åtkomst till statiska objekt.
Rekommenderade mönster för multi-tenant-lösningar i DI
Orchard Core är ett programramverk för att skapa modulära program med flera klienter på ASP.NET Core. För mer information, se dokumentationen om Orchard Core.
Se Orchard Core-exempel för hur du kan bygga modulära och fleranvändarapplikationer endast med Orchard Core Framework utan några av dess CMS-specifika funktioner.
Ramverksbaserade tjänster
Program.cs
registrerar tjänster som appen använder, inklusive plattformsfunktioner som Entity Framework Core och ASP.NET Core MVC. Till en början har IServiceCollection
som tillhandahålls till Program.cs
tjänster som definierats av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.
I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:
Tjänsttyp | Livstid |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Kortvarig |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Övergående |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Kortvarig |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Kortvarig |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ytterligare resurser
- ASP.NET Core Blazor beroendeinmatning
- Beroendeinjektion i vyer i ASP.NET Core
- Beroendeinmatning i styrenheter i ASP.NET Core
- Beroendeinmatning i kravhanterare i ASP.NET Core
- NDC-konferensmönster för DI-apputveckling
- App-start i ASP.NET Core
- Fabriksbaserad aktivering av mellanprogram i ASP.NET Core
- Förstå grunderna för beroendeinmatning i .NET
- riktlinjer för beroendeinmatning
- Självstudie: Använd beroendeinjektion i .NET
- .NET-beroendeinjektion
- ASP.NET GRUNDLÄGGANDE BEROENDEINMATNING: VAD ÄR ISERVICECOLLECTION?
- Fyra sätt att ta bort IDisposables i ASP.NET Core
- Skriva ren kod i ASP.NET Core med beroendeinjektion (MSDN)
- explicita beroendeprincipen
- Inversion av kontrollcontainrar och dependency injection-mönstret (Martin Fowler)
- Registrera en tjänst med flera gränssnitt i ASP.NET Core DI-
Av Kirk Larkin, Steve Smithoch Brandon Dahler
ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå inversion av kontroll (IoC) mellan klasser och deras beroenden.
Mer information om beroendeinmatning i MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.
Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.
Mer information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.
Det här avsnittet innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.
Visa eller ladda ned exempelkod (hur du laddar ned)
Översikt över beroendeinmatning
Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency
-klass med en WriteMessage
metod som andra klasser är beroende av:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
En klass kan skapa en instans av klassen MyDependency
för att använda sin WriteMessage
-metod. I följande exempel är klassen MyDependency
ett beroende av klassen IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Klassen skapar och är direkt beroende av klassen MyDependency
. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:
- Om du vill ersätta
MyDependency
med en annan implementering måste klassenIndexModel
ändras. - Om det finns beroenden för
MyDependency
måste de konfigureras av klassenIndexModel
. I ett stort projekt med flera klasser beroende påMyDependency
blir konfigurationskoden utspridd över appen. - Den här implementeringen är svår att enhetstesta.
Beroendeinsprutning åtgärdar dessa problem genom:
- Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
- Registrering av ett beroende i en tjänstcontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens
Program.cs
-fil. - Inmatning av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.
I exempelappendefinierar IMyDependency
-gränssnittet WriteMessage
-metoden:
public interface IMyDependency
{
void WriteMessage(string message);
}
Det här gränssnittet implementeras av en konkret typ, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Exempelappen registrerar IMyDependency
-tjänsten med den konkreta typen MyDependency
. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran.
Tjänstlivslängder beskrivs senare i det här avsnittet.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
I exempelappen begärs IMyDependency
-tjänsten och används för att anropa metoden WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Genom att använda DI-mönstret, kontrollanten eller Razor sidan:
- Använder inte den konkreta typen
MyDependency
, endast detIMyDependency
-gränssnitt det implementerar. Det gör det enkelt att ändra implementeringen utan att ändra kontrollern eller Razor-sidan. - Skapar inte någon instans av
MyDependency
, den skapas av DI-containern.
Implementeringen av IMyDependency
-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Den uppdaterade Program.cs
registrerar den nya IMyDependency
implementeringen:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
beror på ILogger<TCategoryName>, som den begär i konstruktorn.
ILogger<TCategoryName>
är en tjänst som tillhandahålls av ramverket.
Det är inte ovanligt att använda beroendeinjektion i en kedjad form. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.
Containern löser ILogger<TCategoryName>
genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.
Inom beroendeinsprutningsterminologi är en tjänst:
- Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel
IMyDependency
-tjänsten. - Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.
Ramverket ger ett robust loggning system. De IMyDependency
implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Med hjälp av föregående kod behöver du inte uppdatera Program.cs
eftersom loggning tillhandahålls av ramverket.
Registrera grupper av tjänster med tilläggsmetoder
ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME}
tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion.
AddControllers tilläggsmetod registrerar till exempel de tjänster som krävs för MVC-kontroller.
Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och AddDefaultIdentity:
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Överväg följande som registrerar tjänster och konfigurerar alternativ:
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
var app = builder.Build();
Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
De återstående tjänsterna registreras i en liknande klass. Följande kod använder de nya tilläggsmetoderna för att registrera tjänsterna:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Obs! Varje services.Add{GROUP_NAME}
tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver.
Tjänstlivslängd
Se tjänstlivslängder i beroendeinmatning i .NET
Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:
- Mata in tjänsten i mellanprogrammets
Invoke
- ellerInvokeAsync
-metod. Om du använder konstruktörsinjektion genererar ett körningsfel eftersom det tvingar den avgränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registrering visarInvokeAsync
metod. - Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets konstruktor.
Mer information finns i Skriv anpassad ASP.NET Core-mellanvara.
Metoder för tjänstregistrering
Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET
Det är vanligt att använda flera implementeringar när modelleringstyper för att testa.
Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.
Någon av ovanstående tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton
två gånger med IMyDependency
som tjänsttyp. Det andra anropet till AddSingleton
åsidosätter det föregående när den löses som IMyDependency
och lägger till det föregående när flera tjänster löses upp via IEnumerable<IMyDependency>
. Tjänsterna visas i den ordning de registrerades när de löstes via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Nyckelade tjänster
keyed services refererar till en mekanism för att registrera och hämta DI-tjänster (Dependency Injection) med hjälp av nycklar. En tjänst associeras med en nyckel genom att anropa AddKeyedSingleton (eller AddKeyedScoped
eller AddKeyedTransient
) för att registrera den. Få åtkomst till en registrerad tjänst genom att ange nyckeln med attributet [FromKeyedServices]
. Följande kod visar hur du använder nyckelade tjänster:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
Konstruktorinmatningsbeteende
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Entity Framework-kontexter
Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livslängd anger du livslängden med hjälp av en AddDbContext överlagring. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.
Alternativ för livslängd och registrering
För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId
. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Följande Operation
-klass implementerar alla föregående gränssnitt. Konstruktorn Operation
genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Följande kod skapar flera registreringar av klassen Operation
enligt de namngivna livslängderna:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Exempelappen visar objektlivslängder både inom och mellan begäranden.
IndexModel
och mellanprogram begär varje typ av IOperation
typ och loggar OperationId
för var och en:
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
På samma sätt som i IndexModel
löser mellanprogrammet samma tjänster:
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Begränsade och tillfälliga tjänster måste lösas i metoden InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Loggningsutdata visar:
-
Tillfälliga objekt är alltid olika. Det tillfälliga
OperationId
värdet skiljer sig iIndexModel
och i mellanprogrammet. - Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
- Singleton- objekt är desamma för varje begäran.
Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Lösa en tjänst vid appstart
Följande kod visar hur du löser en begränsad tjänst under en begränsad tid när appen startar:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
app.MapGet("/", () => "Hello World!");
app.Run();
Omfångsverifiering
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Mer information finns i Omfångsverifiering.
Begär tjänster
Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.
Ramverket skapar ett omfång per begäran och RequestServices
exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.
Notera
Föredra att begära beroenden som konstruktorparametrar framför att lösa tjänster från RequestServices
. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.
Utforma tjänster för beroendeinmatning
När du utformar tjänster för beroendeinmatning:
- Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
- Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
- Gör tjänsterna små, välräknade och enkelt testade.
Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.
Avyttring av tjänster
Containern kallar Dispose för de IDisposable typer den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.
I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Tjänster som inte har skapats av tjänstcontainern
Överväg följande kod:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
I föregående kod:
- Tjänstinstanserna skapas inte av tjänstcontainern.
- Ramverket tar inte bort tjänsterna automatiskt.
- Utvecklaren ansvarar för att ta bort tjänsterna.
IDisposable-vägledning för tillfälliga och delade instanser
Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET
Ersättning av standardtjänstcontainer
Se Standardtjänstcontainerersättning i Beroendeinmatning i .NET
Rekommendationer
Se rekommendationer i beroendeinmatning i .NET
Undvik att använda tjänstlokaliserarmönster. Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:
felaktig:
Rätt:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
En annan variant av tjänstlokaliseraren att undvika är att injektera en fabrik som hanterar beroenden vid körning. Båda dessa metoder blandar inversion av kontroll strategier.
Undvik statisk åtkomst till
HttpContext
(till exempel IHttpContextAccessor.HttpContext).
DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med statisk objektåtkomst.
Rekommenderade mönster för flera innehavare i DI
Orchard Core är ett ramverk för att bygga modulära applikationer för flera hyresgäster på ASP.NET Core. För mer information, se dokumentationen om Orchard Core.
Se Orchard Core-exempel exempel på hur du skapar modulära appar och appar för flera klientorganisationer med bara Orchard Core Framework utan någon av dess CMS-specifika funktioner.
Ramverksbaserade tjänster
Program.cs
registrerar tjänster som appen använder, inklusive plattformsfunktioner som Entity Framework Core och ASP.NET Core MVC. Inledningsvis ges IServiceCollection
till Program.cs
tjänster som definierats av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.
I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:
Tjänsttyp | Livstid |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Kortvarig |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Kortvarig |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Kortvarig |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Övergående |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ytterligare resurser
- Beroendeinjektion i vyer i ASP.NET Core
- Beroendeinmatning i styrenheter i ASP.NET Core
- Beroendeinmatning i kravhanterare i ASP.NET Core
- ASP.NET Core Blazor beroendeinmatning
- NDC-konferensmönster för DI-apputveckling
- App-start i ASP.NET Core
- Fabriksbaserad aktivering av mellanprogram i ASP.NET Core
- Förstå grunderna för beroendeinmatning i .NET
- riktlinjer för beroendeinjektion
- Självstudie: Använda beroendeinjektion i .NET
- .NET-beroendeinjektion
- ASP.NET GRUNDLÄGGANDE BEROENDEINMATNING: VAD ÄR ISERVICECOLLECTION?
- Fyra sätt att ta bort IDisposables i ASP.NET Core
- Skriva ren kod i ASP.NET Core med beroendeinjektion (MSDN)
- explicita beroendeprincipen
- Kontrollomkastning och beroendeinjektionsmönstret (Martin Fowler)
- Registrera en tjänst med flera gränssnitt i ASP.NET Core DI-
Av Kirk Larkin, Steve Smithoch Brandon Dahler
ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå Inversion of Control (IoC) mellan klasser och deras beroenden.
Mer information om beroendeinmatning i MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.
Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.
Mer information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.
Det här avsnittet innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.
Visa eller ladda ned exempelkod (hur du laddar ned)
Översikt över beroendeinmatning
Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency
-klass med en WriteMessage
metod som andra klasser är beroende av:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
En klass kan skapa en instans av klassen MyDependency
för att använda sin WriteMessage
-metod. I följande exempel är klassen MyDependency
ett beroende av klassen IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Klassen skapar och är direkt beroende av klassen MyDependency
. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:
- Om du vill ersätta
MyDependency
med en annan implementering måste klassenIndexModel
ändras. - Klassen
IndexModel
måste också konfigurera eventuella beroenden somMyDependency
har. I ett stort projekt med flera klasser beroende påMyDependency
blir konfigurationskoden utspridd över appen. - Den här implementeringen är svår att enhetstesta.
Beroendeinjektion åtgärdar dessa problem genom:
- Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
- Registrering av beroendet i en tjänstcontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens
Program.cs
-fil. - Införande av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.
I exempelappendefinierar IMyDependency
-gränssnittet WriteMessage
-metoden:
public interface IMyDependency
{
void WriteMessage(string message);
}
Det här gränssnittet implementeras av en konkret typ, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Exempelappen registrerar IMyDependency
-tjänsten med den konkreta typen MyDependency
. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran.
Tjänstlivslängder beskrivs senare i det här avsnittet.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
I exempelappen begärs IMyDependency
-tjänsten och används för att anropa metoden WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Genom att använda DI-mönstret, kontrollanten eller Razor sidan:
- Använder inte den konkreta typen
MyDependency
, utan bara detIMyDependency
-gränssnitt som den implementerar. Det gör det enkelt att ändra implementeringen utan att ändra kontrollanten eller Razor sidan. - Skapar inte någon instans av
MyDependency
, den skapas av DI-containern.
Implementeringen av IMyDependency
-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Den uppdaterade Program.cs
registrerar den nya IMyDependency
implementeringen:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
beror på ILogger<TCategoryName>, som begärs i konstruktorn.
ILogger<TCategoryName>
är en tjänst som tillhandahålls av ramverket.
Det är inte ovanligt att använda beroendeinmatning på ett kedjat sätt. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.
Containern löser ILogger<TCategoryName>
genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.
Inom beroendeinjektionsterminologi är en tjänst:
- Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel
IMyDependency
-tjänsten. - Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.
Ramverket ger ett robust loggning system. De IMyDependency
implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; } = string.Empty;
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Med hjälp av föregående kod behöver du inte uppdatera Program.cs
eftersom loggning tillhandahålls av ramverket.
Registrera grupper av tjänster med tilläggsmetoder
ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME}
tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. Metoden AddControllers tilläggsmetod registrerar till exempel de tjänster som krävs för MVC-kontroller.
Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och AddDefaultIdentity:
using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
Överväg följande som registrerar tjänster och konfigurerar alternativ:
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
var app = builder.Build();
Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
De återstående tjänsterna registreras i en liknande klass. Följande kod använder de nya tilläggsmetoderna för att registrera tjänsterna:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Obs! Varje services.Add{GROUP_NAME}
tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver.
Tjänstlivslängd
Se livslängder för tjänster i beroendeinjektion i .NET
Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:
- Mata in tjänsten i mellanprogrammets
Invoke
- ellerInvokeAsync
-metod. Om du använder konstruktorinmatning utlöser ett körningsundantag eftersom det tvingar den begränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registreringsalternativ demonstrerarInvokeAsync
metodiken. - Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets konstruktor.
Mer information finns i Skriv anpassad ASP.NET Core-mellanprogramvara.
Metoder för tjänstregistrering
Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET
Det är vanligt att använda flera implementeringar när modelleringstyper för att testa.
Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.
Någon av ovanstående tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton
två gånger med IMyDependency
som tjänsttyp. Det andra anropet till AddSingleton
åsidosätter det föregående när det löses som IMyDependency
och kompletterar det föregående när flera tjänster löses genom IEnumerable<IMyDependency>
. Tjänsterna visas i den ordning de registrerades när de löstes via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Konstruktorinmatningsbeteende
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Entity Framework-kontexter
Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livstid, ange livstiden med hjälp av en AddDbContext överbelastning. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.
Alternativ för livslängd och registrering
För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId
. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Följande Operation
-klass implementerar alla föregående gränssnitt. Konstruktorn Operation
genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Följande kod skapar flera registreringar av klassen Operation
enligt de namngivna livslängderna:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMyMiddleware();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Exempelappen visar objektlivslängder både inom och mellan begäranden.
IndexModel
och mellanprogram begär varje typ av IOperation
typ och loggar OperationId
för var och en:
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
På samma sätt som i IndexModel
löser mellanprogrammet samma tjänster:
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Omfattade och tillfälliga tjänster måste lösas i metoden InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Loggningsutdata visar:
-
Tillfälliga objekt är alltid olika. Det tillfälliga
OperationId
värdet skiljer sig iIndexModel
och i mellanprogrammet. - Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
- Singleton- objekt är desamma för varje begäran.
Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Lösa en tjänst vid appstart
Följande kod visar hur du löser en begränsad tjänst under en begränsad tid när appen startar:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
app.MapGet("/", () => "Hello World!");
app.Run();
Omfångsverifiering
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Mer information finns i Omfångsverifiering.
Begär tjänster
Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.
Ramverket skapar ett omfång per begäran och RequestServices
exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.
Not
Föredra att begära beroenden som konstruktorparametrar snarare än att lösa tjänster från RequestServices
. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.
Utforma tjänster för beroendeinmatning
När du utformar tjänster för beroendeinjektion:
- Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
- Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
- Gör tjänsterna små, välräknade och enkelt testade.
Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.
Avyttring av tjänster
Containern anropar Dispose för IDisposable-typer den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.
I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
using DIsample2.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
var app = builder.Build();
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Tjänster som inte har skapats av tjänstcontainern
Överväg följande kod:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
I föregående kod:
- Tjänstinstanserna skapas inte av tjänstcontainern.
- Ramverket tar inte bort tjänsterna automatiskt.
- Utvecklaren ansvarar för att ta bort tjänsterna.
Riktlinjer för användning av IDisposable för tillfälliga och delade instanser
Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET
Ersättning av standardtjänstcontainer
Se Standardtjänstcontainerersättning i Beroendeinmatning i .NET
Rekommendationer
Se rekommendationer i beroendeinmatning i .NET
Undvik att använda tjänstlokaliserarmönster. Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:
felaktig:
Rätt:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
En annan variant av tjänstlokaliserare att undvika är att injicera en fabrik som löser beroenden vid körtid. Båda dessa metoder blandar Inversion of Control-strategier.
Undvik statisk åtkomst till
HttpContext
(till exempel IHttpContextAccessor.HttpContext).
DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med statisk objektåtkomst.
Rekommenderade mönster för flera innehavare i DI
Orchard Core är ett programramverk för att skapa modulära program i en fleranvändarmiljö på ASP.NET Core. För mer information, se dokumentationen om Orchard Core.
Se Orchard Core-exempel för exempel på hur man bygger modulära och multi-tenant appar genom att använda endast Orchard Core Framework utan dess CMS-specifika funktioner.
Ramverksbaserade tjänster
Program.cs
registrerar tjänster som appen använder, inklusive plattformsfunktioner som Entity Framework Core och ASP.NET Core MVC. Inledningsvis tillhandahåller IServiceCollection
som ges till Program.cs
tjänster som definieras av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.
I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:
Tjänsttyp | Livstid |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Kortvarig |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Kortvarig |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Kortvarig |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Kortvarig |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ytterligare resurser
- Beroendeinjektion i vyer i ASP.NET Core
- Beroendeinmatning i styrenheter i ASP.NET Core
- Beroendeinmatning i kravhanterare i ASP.NET Core
- ASP.NET Core Blazor beroendeinmatning
- NDC-konferensmönster för DI-apputveckling
- App-start i ASP.NET Core
- Fabriksbaserad aktivering av mellanprogram i ASP.NET Core
- Fyra sätt att ta bort IDisposables i ASP.NET Core
- Skriva ren kod i ASP.NET Core med beroendeinjektion (MSDN)
- explicita beroendeprincipen
- Inversion av kontrollcontainrar och beroendeinmatningsmönstret (Martin Fowler)
- Registrera en tjänst med flera gränssnitt i ASP.NET Core DI-
Av Kirk Larkin, Steve Smith, Scott Addieoch Brandon Dahler
ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå inversion av kontroll (IoC) mellan klasser och deras beroenden.
Mer information om beroendeinmatning i MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.
Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.
Mer information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.
Det här avsnittet innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.
Visa eller ladda ned exempelkod (hur du laddar ned)
Översikt över beroendeinmatning
Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency
-klass med en WriteMessage
metod som andra klasser är beroende av:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
En klass kan skapa en instans av klassen MyDependency
för att använda sin WriteMessage
-metod. I följande exempel är klassen MyDependency
ett beroende av klassen IndexModel
:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet created this message.");
}
}
Klassen skapar och är direkt beroende av klassen MyDependency
. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:
- Om du vill ersätta
MyDependency
med en annan implementering måste klassenIndexModel
ändras. - Om
MyDependency
har beroenden ska de också konfigureras avIndexModel
-klassen. I ett stort projekt med flera klasser beroende påMyDependency
blir konfigurationskoden utspridd över appen. - Den här implementeringen är svår att enhetstesta. Appen bör använda en modell- eller stub-
MyDependency
-klass, vilket inte är möjligt med den här metoden.
Beroendeinjektion åtgärdar dessa problem genom:
- Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
- Registrering av ett beroende i en tjänstekontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens
Startup.ConfigureServices
metod. - Inmatning av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.
I exempelappendefinierar IMyDependency
-gränssnittet WriteMessage
-metoden:
public interface IMyDependency
{
void WriteMessage(string message);
}
Det här gränssnittet implementeras av en konkret typ, MyDependency
:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Exempelappen registrerar IMyDependency
-tjänsten med den konkreta typen MyDependency
. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran.
Tjänstlivslängder beskrivs senare i det här avsnittet.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddRazorPages();
}
I exempelappen begärs IMyDependency
-tjänsten och används för att anropa metoden WriteMessage
:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Med hjälp av DI-mönstret, kontrolleraren:
- Använder inte betongtypen
MyDependency
, bara detIMyDependency
gränssnitt som implementeras. Det gör det enkelt att ändra den implementering som kontrollanten använder utan att ändra kontrollanten. - Skapar inte någon instans av
MyDependency
, den skapas av DI-containern.
Implementeringen av IMyDependency
-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Den uppdaterade ConfigureServices
-metoden registrerar den nya IMyDependency
implementeringen:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency2>();
services.AddRazorPages();
}
MyDependency2
ber om ILogger<TCategoryName>, som den begär i konstruktorn.
ILogger<TCategoryName>
är en tjänst som tillhandahålls av ramverket.
Det är inte ovanligt att använda beroendeinmatning på ett kedjat sätt. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.
Containern löser ILogger<TCategoryName>
genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.
I sambandinmatningsterminologin är en tjänst:
- Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel
IMyDependency
-tjänsten. - Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.
Ramverket ger ett robust loggning system. De IMyDependency
implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras i ConfigureServices
:
public class AboutModel : PageModel
{
private readonly ILogger _logger;
public AboutModel(ILogger<AboutModel> logger)
{
_logger = logger;
}
public string Message { get; set; }
public void OnGet()
{
Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}
Med hjälp av föregående kod behöver du inte uppdatera ConfigureServices
eftersom loggning tillhandahålls av ramverket.
Tjänster som införs i uppstart
Tjänster kan matas in i Startup
konstruktorn och metoden Startup.Configure
.
Endast följande tjänster kan injiceras i konstruktorn Startup
när du använder den generiska värden (IHostBuilder):
Alla tjänster som är registrerade med DI-containern kan matas in i metoden Startup.Configure
:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
...
}
Mer information finns i App-start i ASP.NET Core och Åtkomstkonfiguration i Startup.
Registrera grupper av tjänster med tilläggsmetoder
ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME}
tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. Tilläggsmetoden AddControllers registrerar till exempel de tjänster som krävs för MVC-kontroller.
Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och AddDefaultIdentity:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
}
Överväg följande ConfigureServices
metod, som registrerar tjänster och konfigurerar alternativ:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(
Configuration.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
Configuration.GetSection(ColorOptions.Color));
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
services.AddRazorPages();
}
Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
public static IServiceCollection AddMyDependencyGroup(
this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();
return services;
}
}
}
De återstående tjänsterna registreras i en liknande klass. Följande ConfigureServices
metod använder de nya tilläggsmetoderna för att registrera tjänsterna:
public void ConfigureServices(IServiceCollection services)
{
services.AddConfig(Configuration)
.AddMyDependencyGroup();
services.AddRazorPages();
}
Obs! Varje services.Add{GROUP_NAME}
tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver. Vi rekommenderar att appar följer namngivningskonventionen för att skapa tilläggsmetoder i namnrymden Microsoft.Extensions.DependencyInjection. Skapa tilläggsmetoder i Microsoft.Extensions.DependencyInjection
namnrymd:
- Kapslar in grupper av tjänstregistreringar.
- Ger bekväm IntelliSense åtkomst till tjänsten.
Tjänstlivslängd
Se livslängder för tjänster i beroendeinjektion i .NET
Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:
- Mata in tjänsten i mellanprogrammets
Invoke
- ellerInvokeAsync
-metod. Om du använder konstruktorinmatning utlöser ett körningsundantag eftersom det tvingar den begränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registrering visarInvokeAsync
metod. - Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets
InvokeAsync
-metod.
Mer information finns i Write custom ASP.NET Core middleware.
Metoder för tjänstregistrering
Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET
Det är vanligt att använda flera implementeringar när man mockar typer för testning
Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.
Någon av ovanstående tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton
två gånger med IMyDependency
som tjänsttyp. Det andra anropet till AddSingleton
åsidosätter det föregående när det löses som IMyDependency
och läggs till det föregående när flera tjänster löses via IEnumerable<IMyDependency>
. Tjänsterna visas i den ordning de registrerades när de löstes via IEnumerable<{SERVICE}>
.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Konstruktorinmatningsbeteende
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Entity Framework-kontexter
Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livslängd anger du livslängden med hjälp av en AddDbContext överlagring. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.
Alternativ för livslängd och registrering
För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId
. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Följande Operation
-klass implementerar alla föregående gränssnitt. Konstruktorn Operation
genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId
:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Metoden Startup.ConfigureServices
skapar flera registreringar av klassen Operation
enligt de namngivna livslängderna:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddRazorPages();
}
Exempelappen visar objektlivslängder både inom och mellan begäranden.
IndexModel
och mellanprogrammet begär varje IOperation
typ och loggar OperationId
för var och en:
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;
public IndexModel(ILogger<IndexModel> logger,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void OnGet()
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + _scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
}
}
På samma sätt som i IndexModel
löser mellanprogrammet samma tjänster:
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
IOperationTransient transientOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_singletonOperation = singletonOperation;
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
}
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
Tjänster med omfattning måste lösas i metoden InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Loggningsutdata visar:
-
Tillfälliga objekt är alltid olika. Det tillfälliga
OperationId
värdet skiljer sig iIndexModel
och i mellanprogrammet. - Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
- Singleton- objekt är desamma för varje begäran.
Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json
:
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Anropa tjänster från main
Skapa en IServiceScope med IServiceScopeFactory.CreateScope för att lösa en begränsad tjänst inom appens omfång. Den här metoden är användbar för att komma åt en begränsad tjänst vid start för att köra initieringsuppgifter.
I följande exempel visas hur du kommer åt den begränsade IMyDependency
-tjänsten och anropar dess WriteMessage
-metod i Program.Main
:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var myDependency = services.GetRequiredService<IMyDependency>();
myDependency.WriteMessage("Call services from main");
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Omfångsverifiering
Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET
Mer information finns i Omfångsverifiering.
Begär tjänster
Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.
Ramverket skapar ett omfång per begäran och RequestServices
exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.
Not
Det är bättre att begära beroenden som konstruktorparametrar istället för att lösa tjänster från RequestServices
. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.
Utforma tjänster för beroendeinmatning
När du utformar tjänster för beroendeinjektion:
- Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
- Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
- Gör tjänsterna små, välräknade och enkelt testade.
Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.
Avyttring av tjänster
Containern anropar Dispose för de typer IDisposable som den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.
I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt:
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
public interface IService3
{
public void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
var myKey = Configuration["MyKey"];
services.AddSingleton<IService3>(sp => new Service3(myKey));
services.AddRazorPages();
}
public class IndexModel : PageModel
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public IndexModel(Service1 service1, Service2 service2, IService3 service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
public void OnGet()
{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}
Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose
Tjänster som inte har skapats av tjänstcontainern
Överväg följande kod:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new Service1());
services.AddSingleton(new Service2());
services.AddRazorPages();
}
I föregående kod:
- Tjänstinstanserna skapas inte av tjänstcontainern.
- Ramverket tar inte bort tjänsterna automatiskt.
- Utvecklaren ansvarar för att ta bort tjänsterna.
IDisposable-vägledning för tillfälliga och delade instanser
Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET
Ersättning av standardtjänstcontainer
Se Standardtjänstcontainerersättning i Beroendeinmatning i .NET
Rekommendationer
Se rekommendationer i beroendeinmatning i .NET
Undvik att använda service locator-mönster. Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:
felaktig:
Rätt:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
En annan variant av tjänstlokaliseraren att undvika är att införa en fabrik som resolverar beroenden under körning. Båda dessa metoder blandar inversion av kontroll strategier.
Undvik statisk åtkomst till
HttpContext
(till exempel IHttpContextAccessor.HttpContext).
Undvik anrop till BuildServiceProvider i
ConfigureServices
. Att anropaBuildServiceProvider
sker vanligtvis när utvecklaren vill lösa en tjänst iConfigureServices
. Tänk till exempel på det fall därLoginPath
läses in från konfigurationen. Undvik följande metod:I föregående bild visas följande ASP0000 varning när du väljer den gröna vågiga linjen under
services.BuildServiceProvider
:ASP0000 Att anropa "BuildServiceProvider" från programkoden resulterar i att ytterligare en kopia av singleton-tjänster skapas. Överväg alternativ, till exempel beroendeinjektion av tjänster som parametrar för "Konfigurera".
Att anropa
BuildServiceProvider
skapar en andra container, vilket kan generera inkonsekventa singletons och orsaka referenser till objektdiagram över flera containrar.Ett korrekt sätt att få
LoginPath
är att använda alternativmönstrets inbyggda stöd för DI:public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); services.AddOptions<CookieAuthenticationOptions>( CookieAuthenticationDefaults.AuthenticationScheme) .Configure<IMyService>((options, myService) => { options.LoginPath = myService.GetLoginPath(); }); services.AddRazorPages(); }
Engångstjänster som är tillfälliga samlas in av containern för att bortskaffas. Detta kan förvandlas till en minnesläcka om det löses ut från containern på den översta nivån.
Aktivera omfångsvalidering för att se till att appen inte har singletons som samlar in begränsade tjänster. Mer information finns i Omfångsverifiering.
Precis som med alla uppsättningar med rekommendationer kan det uppstå situationer där det krävs att du ignorerar en rekommendation. Undantag är sällsynta, mestadels specialfall inom själva ramverket.
DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med statisk objektåtkomst.
Rekommenderade mönster för flera innehavare i DI
Orchard Core är ett programramverk för att bygga modulära, fleranvändarapplikationer på ASP.NET Core. För mer information, se dokumentationen om Orchard Core .
Se Orchard Core-exempel för exempel på hur du skapar modulära och fleranvändarappar endast med Orchard Core Framework utan några CMS-specifika funktioner.
Ramverksbaserade tjänster
Metoden Startup.ConfigureServices
registrerar tjänster som appen använder, inklusive plattformsfunktioner, till exempel Entity Framework Core och ASP.NET Core MVC. Till en början har IServiceCollection
som tillhandahålls till ConfigureServices
tjänster som definierats av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.
I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:
Tjänsttyp | Livstid |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Kortvarig |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Övergående |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Kortvarig |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Kortvarig |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Ytterligare resurser
- Beroendeinjektion i vyer i ASP.NET Core
- Beroendeinmatning i styrenheter i ASP.NET Core
- Beroendeinmatning i kravhanterare i ASP.NET Core
- ASP.NET Core Blazor dependensinjektion
- NDC-konferensmönster för DI-apputveckling
- Applikationsstart i ASP.NET Core
- Fabriksbaserad aktivering av mellanprogram i ASP.NET Core
- Fyra sätt att ta bort IDisposables i ASP.NET Core
- skriva ren kod i ASP.NET Core med beroendeinjektion (MSDN)
- explicita beroendeprincipen
- Kontrollinversion och beroendeinjektionsmönstret (Martin Fowler)
- Registrera en tjänst med flera gränssnitt i ASP.NET Core DI-
ASP.NET Core