Injektáž závislostí v ASP.NET Core
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete v tomto článku ve verzi .NET 9.
Kirk Larkin, Steve Smith a Brandon Dahler
ASP.NET Core podporuje vzor návrhu softwaru pro injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.
Další informace specifické pro injektáž závislostí v řadičích MVC najdete v tématu Injektáž závislostí do kontrolerů v ASP.NET Core.
Informace o použití injektáže závislostí v jiných aplikacích než ve webových aplikacích naleznete v tématu Injektáž závislostí v .NET.
Další informace o injektáži závislostí možností najdete v tématu Vzor možnosti v ASP.NET Core.
Toto téma obsahuje informace o injektáži závislostí v ASP.NET Core. Primární dokumentace k použití injektáže závislostí je obsažena v injektáži závislostí v .NET.
Pokyny Blazor k DI, které přidávají nebo nahrazují pokyny v tomto článku, najdete v tématu ASP.NET injektáž závislostí jádraBlazor.
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Přehled injektáže závislostí
Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MyDependency
třídu pomocí WriteMessage
metody, na které závisí jiné třídy:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Třída může vytvořit instanci MyDependency
třídy, aby využívala její WriteMessage
metodu. V následujícím příkladu MyDependency
je třída závislostí IndexModel
třídy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Třída vytvoří a přímo závisí na MyDependency
třídě. Závislosti kódu, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:
- Chcete-li nahradit
MyDependency
jinou implementací,IndexModel
musí být třída změněna. - Pokud
MyDependency
obsahuje závislosti, musí je také nakonfigurovatIndexModel
třída. Ve velkém projektu s více třídami v závislosti naMyDependency
tom se konfigurační kód rozdělí v aplikaci. - Tato implementace je obtížná pro testování jednotek.
Injektáž závislostí řeší tyto problémy prostřednictvím:
- Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
- Registrace závislosti v kontejneru služby ASP.NET Core poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v souboru aplikace
Program.cs
. - Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.
V ukázkové aplikaciIMyDependency
rozhraní definuje metoduWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Toto rozhraní je implementováno konkrétním typem: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Ukázková aplikace zaregistruje IMyDependency
službu s konkrétním typem MyDependency
. Metoda AddScoped zaregistruje službu s vymezenou životností, životností jednoho požadavku. Životnosti služeb jsou popsány dále v tomto tématu.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
V ukázkové aplikaci IMyDependency
se služba vyžádá a použije se k volání WriteMessage
metody:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Pomocí vzoru DI kontroler nebo Razor stránka:
- Nepoužívá konkrétní typ
MyDependency
, pouze rozhraní, kteréIMyDependency
implementuje. To usnadňuje změnu implementace beze změny kontroleru nebo Razor stránky. - Nevytvoří instanci
MyDependency
, je vytvořená kontejnerem DI.
Implementaci rozhraní je možné vylepšit pomocí integrovaného IMyDependency
rozhraní API pro protokolování:
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}");
}
}
Aktualizovaná Program.cs
registrace nové IMyDependency
implementace:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>
je služba poskytovaná architekturou.
Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.
Kontejner se ILogger<TCategoryName>
vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.
V terminologii injektáže závislostí služba:
- Je obvykle objekt, který poskytuje službu jiným objektům, jako
IMyDependency
je služba. - Nesouvisí s webovou službou, i když služba může používat webovou službu.
Architektura poskytuje robustní systém protokolování . Implementace IMyDependency
uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které nevyžaduje registraci žádné služby:
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);
}
}
Použití předchozího kódu není nutné aktualizovat Program.cs
, protože protokolování je poskytováno architekturou.
Registrace skupin služeb pomocí rozšiřujících metod
Architektura ASP.NET Core používá konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME}
rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. AddControllers Například metoda rozšíření registruje služby vyžadované pro kontrolery MVC.
Následující kód je generován šablonou Razor Pages pomocí jednotlivých uživatelských účtů a ukazuje, jak přidat další služby do kontejneru pomocí rozšiřujících metod AddDbContext a 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();
Vezměme například následující kód, který registruje služby a konfiguruje možnosti:
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();
Související skupiny registrací lze přesunout do rozšiřující metody pro registraci služeb. Například konfigurační služby jsou přidány do následující třídy:
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;
}
}
}
Ostatní služby jsou registrovány v podobné třídě. Následující kód používá nové rozšiřující metody k registraci služeb:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Poznámka Každá rozšiřující metoda services.Add{GROUP_NAME}
přidává a potenciálně konfiguruje služby. AddControllersWithViews například přidá kontrolery služeb MVC s požadavkem zobrazení a AddRazorPages přidá požadavky Razor Pages pro služby.
Životnost služeb
Zobrazení životnosti služby v injektáži závislostí v .NET
Pokud chcete v middlewaru používat omezené služby, použijte jeden z následujících přístupů:
- Vložte službu do middlewaru
Invoke
neboInvokeAsync
metody. Použití injektáže konstruktoru vyvolá výjimku modulu runtime, protože vynutí, aby se vymezená služba chovala jako singleton. Ukázka v části Možnosti životnosti a registrace ukazujeInvokeAsync
přístup. - Použijte middleware založený na továrně. Middleware zaregistrovaný pomocí tohoto přístupu se aktivuje na požadavek klienta (připojení), který umožňuje vkládání vymezených služeb do konstruktoru middlewaru.
Další informace najdete v tématu Psaní vlastního middlewaru ASP.NET Core.
Metody registrace služby
Viz Metody registrace služby v injektáži závislostí v .NET
Při testování je běžné používat více implementací.
Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .
K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton
se jako typ služby volá dvakrát IMyDependency
. Druhé volání, které AddSingleton
přepíše předchozí, když je vyřešeno jako IMyDependency
a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMyDependency>
. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím 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);
}
}
Služby s klíči
Služby s klíči odkazují na mechanismus registrace a načítání služeb injektáže závislostí (DI) pomocí klíčů. Služba je přidružena ke klíči voláním AddKeyedSingleton (nebo AddKeyedScoped
AddKeyedTransient
) k jeho registraci. Přístup k registrované službě zadáním klíče s atributem [FromKeyedServices]
. Následující kód ukazuje, jak používat služby s klíči:
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"));
}
}
Služby s klíči v middlewaru
Middleware podporuje služby s klíči v konstruktoru Invoke
/InvokeAsync
i metodě:
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);
}
Další informace o vytváření middlewaru najdete v tématu Psaní vlastního middlewaru ASP.NET Core.
Chování injektáže konstruktoru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Kontexty Entity Frameworku
Ve výchozím nastavení se kontexty Entity Framework přidají do kontejneru služby pomocí doby života s vymezeným oborem, protože databázové operace webové aplikace jsou obvykle vymezeny na požadavek klienta. Pokud chcete použít jinou životnost, zadejte životnost pomocí AddDbContext přetížení. Služby daného životního cyklu by neměly používat kontext databáze s dobou života kratší než životnost služby.
Možnosti životnosti a registrace
Abychom si ukázali rozdíl mezi životnostmi služeb a možnostmi jejich registrace, zvažte následující rozhraní, která představují úlohu jako operaci s identifikátorem. OperationId
V závislosti na tom, jak je pro následující rozhraní nakonfigurovaná doba života služby operace, kontejner při vyžádání třídou poskytuje buď stejné, nebo různé instance služby:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Následující Operation
třída implementuje všechna předchozí rozhraní. Konstruktor Operation
vygeneruje identifikátor GUID a uloží poslední 4 znaky ve OperationId
vlastnosti:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Následující kód vytvoří více registrací Operation
třídy podle pojmenovaných životností:
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();
Ukázková aplikace ukazuje životnost objektů v rámci požadavků i mezi požadavky. Middleware IndexModel
a požadavek na každý typ IOperation
a protokolovat OperationId
pro každý typ:
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);
}
}
IndexModel
Podobně jako middleware řeší stejné služby:
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>();
}
}
V metodě musí být vyřešeny InvokeAsync
omezené a přechodné služby:
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);
}
Výstup protokolovacího modulu ukazuje:
- Přechodné objekty se vždy liší. Přechodná
OperationId
hodnota se v middlewaruIndexModel
a v prostředním prostředí liší. - Objekty s vymezeným oborem jsou pro daný požadavek stejné, ale liší se v rámci každého nového požadavku.
- Jednoúčelové objekty jsou pro každý požadavek stejné.
Pokud chcete snížit výstup protokolování, nastavte v appsettings.Development.json
souboru "Logging:LogLevel:Microsoft:Error":
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Řešení potíží se službou při spuštění aplikace
Následující kód ukazuje, jak vyřešit omezenou dobu trvání služby s vymezeným oborem při spuštění aplikace:
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();
Ověření oboru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Další informace najdete v tématu Ověření oboru.
Vyžádat služby
Služby a jejich závislosti v rámci požadavku ASP.NET Core jsou zpřístupněny prostřednictvím HttpContext.RequestServices.
Architektura vytvoří obor na požadavek a RequestServices
zpřístupňuje vymezeného poskytovatele služeb. Všechny služby s vymezeným oborem jsou platné, pokud je požadavek aktivní.
Poznámka:
Upřednostňujte vyžádání závislostí jako parametrů konstruktoru před překladem služeb z RequestServices
. Vyžádání závislostí jako parametrů konstruktoru poskytuje třídy, které jsou snadnější testovat.
Návrh služeb pro injektáž závislostí
Při navrhování služeb pro injektáž závislostí:
- Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
- Vyhněte se přímé instanci závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
- Zpřístupnit služby malými, dobře faktorovanými a snadno otestovanými.
Pokud má třída velké množství vloženého závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu SRP (Single Responsibility Principle). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd. Mějte na paměti, že Razor třídy modelu stránky Pages a třídy kontroleru MVC by se měly zaměřit na otázky uživatelského rozhraní.
Likvidace služeb
Kontejner volá Dispose typy, které IDisposable vytvoří. Služby vyřešené z kontejneru by nikdy neměly být uvolněny vývojářem. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.
V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky uvolněny: 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");
}
}
Po každé aktualizaci stránky indexu se v konzole ladění zobrazí následující výstup:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Služby, které se nevytvořily kontejnerem služby
Uvažujte následující kód:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
V předchozím kódu:
- Instance služby se nevytvořily kontejnerem služby.
- Architektura nelikviduje služby automaticky.
- Vývojář zodpovídá za likvidaci služeb.
Pokyny pro IDisposable pro přechodné a sdílené instance
Viz pokyny pro IDisposable pro přechodné a sdílené instance v injektáži závislostí v .NET.
Nahrazení výchozího kontejneru služby
Viz Nahrazení výchozího kontejneru služby v injektáži závislostí v .NET
Doporučení
Viz doporučení injektáž závislostí v .NET
Nepoužívejte vzor lokátoru služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci:
Nesprávně:
Správně:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Jinou variantou lokátoru služby, která se vyhne, je vložení továrny, která řeší závislosti za běhu. Obě tyto postupy se směšují s inverzí strategií řízení .
Vyhněte se statickému
HttpContext
přístupu (například IHttpContextAccessor.HttpContext).
Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.
Doporučené vzory pro víceklientské architektury v DI
Orchard Core je aplikační architektura pro vytváření modulárních aplikací s více tenanty na ASP.NET Core. Další informace najdete v dokumentaci k sadovému jádru.
Služby poskytované architekturou
Program.cs
zaregistruje služby, které aplikace používá, včetně funkcí platformy, jako jsou Entity Framework Core a ASP.NET Core MVC. Na začátku má poskytované Program.cs
služby definované architekturou v závislosti na tom, IServiceCollection
jak byl hostitel nakonfigurován. Pro aplikace založené na šablonách ASP.NET Core zaregistruje architektura více než 250 služeb.
Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:
Typ služby | Životnost |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Přechodná |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Přechodná |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Přechodná |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Přechodná |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Další materiály
- Injektáž závislostí do zobrazení v ASP.NET Core
- Injektáž závislostí do kontrolerů v ASP.NET Core
- Injektáž závislostí v obslužných rutinách požadavků v ASP.NET Core
- injektáž závislostí jádra Blazor ASP.NET
- Vzory konferencí NDC pro vývoj aplikací DI
- Spuštění aplikace v ASP.NET Core
- Aktivace middlewaru na základě objektu pro vytváření v ASP.NET Core
- Vysvětlení základů injektáže závislostí v .NET
- Pokyny pro injektáž závislostí
- Kurz: Použití injektáže závislostí v .NET
- Injektáž závislostí .NET
- ASP.NET INJEKTÁŽ ZÁVISLOSTÍ JÁDRA: CO JE ISERVICECOLLECTION?
- Čtyři způsoby odstranění IDisposables v ASP.NET Core
- Psaní čistého kódu v ASP.NET jádru pomocí injektáže závislostí (MSDN)
- Explicitní závislosti – princip
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler)
- Postup registrace služby s více rozhraními v ASP.NET Core DI
Kirk Larkin, Steve Smith a Brandon Dahler
ASP.NET Core podporuje vzor návrhu softwaru pro injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.
Další informace specifické pro injektáž závislostí v řadičích MVC najdete v tématu Injektáž závislostí do kontrolerů v ASP.NET Core.
Informace o použití injektáže závislostí v jiných aplikacích než ve webových aplikacích naleznete v tématu Injektáž závislostí v .NET.
Další informace o injektáži závislostí možností najdete v tématu Vzor možnosti v ASP.NET Core.
Toto téma obsahuje informace o injektáži závislostí v ASP.NET Core. Primární dokumentace k použití injektáže závislostí je obsažena v injektáži závislostí v .NET.
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Přehled injektáže závislostí
Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MyDependency
třídu pomocí WriteMessage
metody, na které závisí jiné třídy:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Třída může vytvořit instanci MyDependency
třídy, aby využívala její WriteMessage
metodu. V následujícím příkladu MyDependency
je třída závislostí IndexModel
třídy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Třída vytvoří a přímo závisí na MyDependency
třídě. Závislosti kódu, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:
- Chcete-li nahradit
MyDependency
jinou implementací,IndexModel
musí být třída změněna. - Pokud
MyDependency
obsahuje závislosti, musí je také nakonfigurovatIndexModel
třída. Ve velkém projektu s více třídami v závislosti naMyDependency
tom se konfigurační kód rozdělí v aplikaci. - Tato implementace je obtížná pro testování jednotek.
Injektáž závislostí řeší tyto problémy prostřednictvím:
- Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
- Registrace závislosti v kontejneru služby ASP.NET Core poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v souboru aplikace
Program.cs
. - Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.
V ukázkové aplikaciIMyDependency
rozhraní definuje metoduWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Toto rozhraní je implementováno konkrétním typem: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Ukázková aplikace zaregistruje IMyDependency
službu s konkrétním typem MyDependency
. Metoda AddScoped zaregistruje službu s vymezenou životností, životností jednoho požadavku. Životnosti služeb jsou popsány dále v tomto tématu.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
V ukázkové aplikaci IMyDependency
se služba vyžádá a použije se k volání WriteMessage
metody:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Pomocí vzoru DI kontroler nebo Razor stránka:
- Nepoužívá konkrétní typ
MyDependency
, pouze rozhraní, kteréIMyDependency
implementuje. To usnadňuje změnu implementace beze změny kontroleru nebo Razor stránky. - Nevytvoří instanci
MyDependency
, je vytvořená kontejnerem DI.
Implementaci rozhraní je možné vylepšit pomocí integrovaného IMyDependency
rozhraní API pro protokolování:
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}");
}
}
Aktualizovaná Program.cs
registrace nové IMyDependency
implementace:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>
je služba poskytovaná architekturou.
Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.
Kontejner se ILogger<TCategoryName>
vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.
V terminologii injektáže závislostí služba:
- Je obvykle objekt, který poskytuje službu jiným objektům, jako
IMyDependency
je služba. - Nesouvisí s webovou službou, i když služba může používat webovou službu.
Architektura poskytuje robustní systém protokolování . Implementace IMyDependency
uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které nevyžaduje registraci žádné služby:
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);
}
}
Použití předchozího kódu není nutné aktualizovat Program.cs
, protože protokolování je poskytováno architekturou.
Registrace skupin služeb pomocí rozšiřujících metod
Architektura ASP.NET Core používá konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME}
rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. AddControllers Například metoda rozšíření registruje služby vyžadované pro kontrolery MVC.
Následující kód je generován šablonou Razor Pages pomocí jednotlivých uživatelských účtů a ukazuje, jak přidat další služby do kontejneru pomocí rozšiřujících metod AddDbContext a 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();
Vezměme například následující kód, který registruje služby a konfiguruje možnosti:
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();
Související skupiny registrací lze přesunout do rozšiřující metody pro registraci služeb. Například konfigurační služby jsou přidány do následující třídy:
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;
}
}
}
Ostatní služby jsou registrovány v podobné třídě. Následující kód používá nové rozšiřující metody k registraci služeb:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Poznámka Každá rozšiřující metoda services.Add{GROUP_NAME}
přidává a potenciálně konfiguruje služby. AddControllersWithViews například přidá kontrolery služeb MVC s požadavkem zobrazení a AddRazorPages přidá požadavky Razor Pages pro služby.
Životnost služeb
Zobrazení životnosti služby v injektáži závislostí v .NET
Pokud chcete v middlewaru používat omezené služby, použijte jeden z následujících přístupů:
- Vložte službu do middlewaru
Invoke
neboInvokeAsync
metody. Použití injektáže konstruktoru vyvolá výjimku modulu runtime, protože vynutí, aby se vymezená služba chovala jako singleton. Ukázka v části Možnosti životnosti a registrace ukazujeInvokeAsync
přístup. - Použijte middleware založený na továrně. Middleware zaregistrovaný pomocí tohoto přístupu se aktivuje na požadavek klienta (připojení), který umožňuje vkládání vymezených služeb do konstruktoru middlewaru.
Další informace najdete v tématu Psaní vlastního middlewaru ASP.NET Core.
Metody registrace služby
Viz Metody registrace služby v injektáži závislostí v .NET
Při testování je běžné používat více implementací.
Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .
K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton
se jako typ služby volá dvakrát IMyDependency
. Druhé volání, které AddSingleton
přepíše předchozí, když je vyřešeno jako IMyDependency
a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMyDependency>
. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím 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);
}
}
Služby s klíči
Služby s klíči odkazují na mechanismus registrace a načítání služeb injektáže závislostí (DI) pomocí klíčů. Služba je přidružena ke klíči voláním AddKeyedSingleton (nebo AddKeyedScoped
AddKeyedTransient
) k jeho registraci. Přístup k registrované službě zadáním klíče s atributem [FromKeyedServices]
. Následující kód ukazuje, jak používat služby s klíči:
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"));
}
}
Chování injektáže konstruktoru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Kontexty Entity Frameworku
Ve výchozím nastavení se kontexty Entity Framework přidají do kontejneru služby pomocí doby života s vymezeným oborem, protože databázové operace webové aplikace jsou obvykle vymezeny na požadavek klienta. Pokud chcete použít jinou životnost, zadejte životnost pomocí AddDbContext přetížení. Služby daného životního cyklu by neměly používat kontext databáze s dobou života kratší než životnost služby.
Možnosti životnosti a registrace
Abychom si ukázali rozdíl mezi životnostmi služeb a možnostmi jejich registrace, zvažte následující rozhraní, která představují úlohu jako operaci s identifikátorem. OperationId
V závislosti na tom, jak je pro následující rozhraní nakonfigurovaná doba života služby operace, kontejner při vyžádání třídou poskytuje buď stejné, nebo různé instance služby:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Následující Operation
třída implementuje všechna předchozí rozhraní. Konstruktor Operation
vygeneruje identifikátor GUID a uloží poslední 4 znaky ve OperationId
vlastnosti:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Následující kód vytvoří více registrací Operation
třídy podle pojmenovaných životností:
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();
Ukázková aplikace ukazuje životnost objektů v rámci požadavků i mezi požadavky. Middleware IndexModel
a požadavek na každý typ IOperation
a protokolovat OperationId
pro každý typ:
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);
}
}
IndexModel
Podobně jako middleware řeší stejné služby:
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>();
}
}
V metodě musí být vyřešeny InvokeAsync
omezené a přechodné služby:
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);
}
Výstup protokolovacího modulu ukazuje:
- Přechodné objekty se vždy liší. Přechodná
OperationId
hodnota se v middlewaruIndexModel
a v prostředním prostředí liší. - Objekty s vymezeným oborem jsou pro daný požadavek stejné, ale liší se v rámci každého nového požadavku.
- Jednoúčelové objekty jsou pro každý požadavek stejné.
Pokud chcete snížit výstup protokolování, nastavte v appsettings.Development.json
souboru "Logging:LogLevel:Microsoft:Error":
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Řešení potíží se službou při spuštění aplikace
Následující kód ukazuje, jak vyřešit omezenou dobu trvání služby s vymezeným oborem při spuštění aplikace:
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();
Ověření oboru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Další informace najdete v tématu Ověření oboru.
Vyžádat služby
Služby a jejich závislosti v rámci požadavku ASP.NET Core jsou zpřístupněny prostřednictvím HttpContext.RequestServices.
Architektura vytvoří obor na požadavek a RequestServices
zpřístupňuje vymezeného poskytovatele služeb. Všechny služby s vymezeným oborem jsou platné, pokud je požadavek aktivní.
Poznámka:
Upřednostňujte vyžádání závislostí jako parametrů konstruktoru před překladem služeb z RequestServices
. Vyžádání závislostí jako parametrů konstruktoru poskytuje třídy, které jsou snadnější testovat.
Návrh služeb pro injektáž závislostí
Při navrhování služeb pro injektáž závislostí:
- Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
- Vyhněte se přímé instanci závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
- Zpřístupnit služby malými, dobře faktorovanými a snadno otestovanými.
Pokud má třída velké množství vloženého závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu SRP (Single Responsibility Principle). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd. Mějte na paměti, že Razor třídy modelu stránky Pages a třídy kontroleru MVC by se měly zaměřit na otázky uživatelského rozhraní.
Likvidace služeb
Kontejner volá Dispose typy, které IDisposable vytvoří. Služby vyřešené z kontejneru by nikdy neměly být uvolněny vývojářem. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.
V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky uvolněny: 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");
}
}
Po každé aktualizaci stránky indexu se v konzole ladění zobrazí následující výstup:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Služby, které se nevytvořily kontejnerem služby
Uvažujte následující kód:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
V předchozím kódu:
- Instance služby se nevytvořily kontejnerem služby.
- Architektura nelikviduje služby automaticky.
- Vývojář zodpovídá za likvidaci služeb.
Pokyny pro IDisposable pro přechodné a sdílené instance
Viz pokyny pro IDisposable pro přechodné a sdílené instance v injektáži závislostí v .NET.
Nahrazení výchozího kontejneru služby
Viz Nahrazení výchozího kontejneru služby v injektáži závislostí v .NET
Doporučení
Viz doporučení injektáž závislostí v .NET
Nepoužívejte vzor lokátoru služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci:
Nesprávně:
Správně:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Jinou variantou lokátoru služby, která se vyhne, je vložení továrny, která řeší závislosti za běhu. Obě tyto postupy se směšují s inverzí strategií řízení .
Vyhněte se statickému
HttpContext
přístupu (například IHttpContextAccessor.HttpContext).
Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.
Doporučené vzory pro víceklientské architektury v DI
Orchard Core je aplikační architektura pro vytváření modulárních aplikací s více tenanty na ASP.NET Core. Další informace najdete v dokumentaci k sadovému jádru.
Služby poskytované architekturou
Program.cs
zaregistruje služby, které aplikace používá, včetně funkcí platformy, jako jsou Entity Framework Core a ASP.NET Core MVC. Na začátku má poskytované Program.cs
služby definované architekturou v závislosti na tom, IServiceCollection
jak byl hostitel nakonfigurován. Pro aplikace založené na šablonách ASP.NET Core zaregistruje architektura více než 250 služeb.
Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:
Typ služby | Životnost |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Přechodná |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Přechodná |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Přechodná |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Přechodná |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Další materiály
- Injektáž závislostí do zobrazení v ASP.NET Core
- Injektáž závislostí do kontrolerů v ASP.NET Core
- Injektáž závislostí v obslužných rutinách požadavků v ASP.NET Core
- injektáž závislostí jádra Blazor ASP.NET
- Vzory konferencí NDC pro vývoj aplikací DI
- Spuštění aplikace v ASP.NET Core
- Aktivace middlewaru na základě objektu pro vytváření v ASP.NET Core
- Vysvětlení základů injektáže závislostí v .NET
- Pokyny pro injektáž závislostí
- Kurz: Použití injektáže závislostí v .NET
- Injektáž závislostí .NET
- ASP.NET INJEKTÁŽ ZÁVISLOSTÍ JÁDRA: CO JE ISERVICECOLLECTION?
- Čtyři způsoby odstranění IDisposables v ASP.NET Core
- Psaní čistého kódu v ASP.NET jádru pomocí injektáže závislostí (MSDN)
- Explicitní závislosti – princip
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler)
- Postup registrace služby s více rozhraními v ASP.NET Core DI
Kirk Larkin, Steve Smith a Brandon Dahler
ASP.NET Core podporuje vzor návrhu softwaru pro injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.
Další informace specifické pro injektáž závislostí v řadičích MVC najdete v tématu Injektáž závislostí do kontrolerů v ASP.NET Core.
Informace o použití injektáže závislostí v jiných aplikacích než ve webových aplikacích naleznete v tématu Injektáž závislostí v .NET.
Další informace o injektáži závislostí možností najdete v tématu Vzor možnosti v ASP.NET Core.
Toto téma obsahuje informace o injektáži závislostí v ASP.NET Core. Primární dokumentace k použití injektáže závislostí je obsažena v injektáži závislostí v .NET.
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Přehled injektáže závislostí
Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MyDependency
třídu pomocí WriteMessage
metody, na které závisí jiné třídy:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Třída může vytvořit instanci MyDependency
třídy, aby využívala její WriteMessage
metodu. V následujícím příkladu MyDependency
je třída závislostí IndexModel
třídy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Třída vytvoří a přímo závisí na MyDependency
třídě. Závislosti kódu, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:
- Chcete-li nahradit
MyDependency
jinou implementací,IndexModel
musí být třída změněna. - Pokud
MyDependency
obsahuje závislosti, musí je také nakonfigurovatIndexModel
třída. Ve velkém projektu s více třídami v závislosti naMyDependency
tom se konfigurační kód rozdělí v aplikaci. - Tato implementace je obtížná pro testování jednotek.
Injektáž závislostí řeší tyto problémy prostřednictvím:
- Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
- Registrace závislosti v kontejneru služby ASP.NET Core poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v souboru aplikace
Program.cs
. - Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.
V ukázkové aplikaciIMyDependency
rozhraní definuje metoduWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Toto rozhraní je implementováno konkrétním typem: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Ukázková aplikace zaregistruje IMyDependency
službu s konkrétním typem MyDependency
. Metoda AddScoped zaregistruje službu s vymezenou životností, životností jednoho požadavku. Životnosti služeb jsou popsány dále v tomto tématu.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
V ukázkové aplikaci IMyDependency
se služba vyžádá a použije se k volání WriteMessage
metody:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Pomocí vzoru DI kontroler nebo Razor stránka:
- Nepoužívá konkrétní typ
MyDependency
, pouze rozhraní, kteréIMyDependency
implementuje. To usnadňuje změnu implementace beze změny kontroleru nebo Razor stránky. - Nevytvoří instanci
MyDependency
, je vytvořená kontejnerem DI.
Implementaci rozhraní je možné vylepšit pomocí integrovaného IMyDependency
rozhraní API pro protokolování:
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}");
}
}
Aktualizovaná Program.cs
registrace nové IMyDependency
implementace:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>
je služba poskytovaná architekturou.
Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.
Kontejner se ILogger<TCategoryName>
vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.
V terminologii injektáže závislostí služba:
- Je obvykle objekt, který poskytuje službu jiným objektům, jako
IMyDependency
je služba. - Nesouvisí s webovou službou, i když služba může používat webovou službu.
Architektura poskytuje robustní systém protokolování . Implementace IMyDependency
uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které nevyžaduje registraci žádné služby:
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);
}
}
Použití předchozího kódu není nutné aktualizovat Program.cs
, protože protokolování je poskytováno architekturou.
Registrace skupin služeb pomocí rozšiřujících metod
Architektura ASP.NET Core používá konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME}
rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. AddControllers Například metoda rozšíření registruje služby vyžadované pro kontrolery MVC.
Následující kód je generován šablonou Razor Pages pomocí jednotlivých uživatelských účtů a ukazuje, jak přidat další služby do kontejneru pomocí rozšiřujících metod AddDbContext a 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();
Vezměme například následující kód, který registruje služby a konfiguruje možnosti:
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();
Související skupiny registrací lze přesunout do rozšiřující metody pro registraci služeb. Například konfigurační služby jsou přidány do následující třídy:
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;
}
}
}
Ostatní služby jsou registrovány v podobné třídě. Následující kód používá nové rozšiřující metody k registraci služeb:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Poznámka Každá rozšiřující metoda services.Add{GROUP_NAME}
přidává a potenciálně konfiguruje služby. AddControllersWithViews například přidá kontrolery služeb MVC s požadavkem zobrazení a AddRazorPages přidá požadavky Razor Pages pro služby.
Životnost služeb
Zobrazení životnosti služby v injektáži závislostí v .NET
Pokud chcete v middlewaru používat omezené služby, použijte jeden z následujících přístupů:
- Vložte službu do middlewaru
Invoke
neboInvokeAsync
metody. Použití injektáže konstruktoru vyvolá výjimku modulu runtime, protože vynutí, aby se vymezená služba chovala jako singleton. Ukázka v části Možnosti životnosti a registrace ukazujeInvokeAsync
přístup. - Použijte middleware založený na továrně. Middleware zaregistrovaný pomocí tohoto přístupu se aktivuje na požadavek klienta (připojení), který umožňuje vkládání vymezených služeb do konstruktoru middlewaru.
Další informace najdete v tématu Psaní vlastního middlewaru ASP.NET Core.
Metody registrace služby
Viz Metody registrace služby v injektáži závislostí v .NET
Při testování je běžné používat více implementací.
Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .
K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton
se jako typ služby volá dvakrát IMyDependency
. Druhé volání, které AddSingleton
přepíše předchozí, když je vyřešeno jako IMyDependency
a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMyDependency>
. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím 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);
}
}
Chování injektáže konstruktoru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Kontexty Entity Frameworku
Ve výchozím nastavení se kontexty Entity Framework přidají do kontejneru služby pomocí doby života s vymezeným oborem, protože databázové operace webové aplikace jsou obvykle vymezeny na požadavek klienta. Pokud chcete použít jinou životnost, zadejte životnost pomocí AddDbContext přetížení. Služby daného životního cyklu by neměly používat kontext databáze s dobou života kratší než životnost služby.
Možnosti životnosti a registrace
Abychom si ukázali rozdíl mezi životnostmi služeb a možnostmi jejich registrace, zvažte následující rozhraní, která představují úlohu jako operaci s identifikátorem. OperationId
V závislosti na tom, jak je pro následující rozhraní nakonfigurovaná doba života služby operace, kontejner při vyžádání třídou poskytuje buď stejné, nebo různé instance služby:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Následující Operation
třída implementuje všechna předchozí rozhraní. Konstruktor Operation
vygeneruje identifikátor GUID a uloží poslední 4 znaky ve OperationId
vlastnosti:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Následující kód vytvoří více registrací Operation
třídy podle pojmenovaných životností:
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();
Ukázková aplikace ukazuje životnost objektů v rámci požadavků i mezi požadavky. Middleware IndexModel
a požadavek na každý typ IOperation
a protokolovat OperationId
pro každý typ:
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);
}
}
IndexModel
Podobně jako middleware řeší stejné služby:
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>();
}
}
V metodě musí být vyřešeny InvokeAsync
omezené a přechodné služby:
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);
}
Výstup protokolovacího modulu ukazuje:
- Přechodné objekty se vždy liší. Přechodná
OperationId
hodnota se v middlewaruIndexModel
a v prostředním prostředí liší. - Objekty s vymezeným oborem jsou pro daný požadavek stejné, ale liší se v rámci každého nového požadavku.
- Jednoúčelové objekty jsou pro každý požadavek stejné.
Pokud chcete snížit výstup protokolování, nastavte v appsettings.Development.json
souboru "Logging:LogLevel:Microsoft:Error":
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Řešení potíží se službou při spuštění aplikace
Následující kód ukazuje, jak vyřešit omezenou dobu trvání služby s vymezeným oborem při spuštění aplikace:
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();
Ověření oboru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Další informace najdete v tématu Ověření oboru.
Vyžádat služby
Služby a jejich závislosti v rámci požadavku ASP.NET Core jsou zpřístupněny prostřednictvím HttpContext.RequestServices.
Architektura vytvoří obor na požadavek a RequestServices
zpřístupňuje vymezeného poskytovatele služeb. Všechny služby s vymezeným oborem jsou platné, pokud je požadavek aktivní.
Poznámka:
Upřednostňujte vyžádání závislostí jako parametrů konstruktoru před překladem služeb z RequestServices
. Vyžádání závislostí jako parametrů konstruktoru poskytuje třídy, které jsou snadnější testovat.
Návrh služeb pro injektáž závislostí
Při navrhování služeb pro injektáž závislostí:
- Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
- Vyhněte se přímé instanci závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
- Zpřístupnit služby malými, dobře faktorovanými a snadno otestovanými.
Pokud má třída velké množství vloženého závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu SRP (Single Responsibility Principle). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd. Mějte na paměti, že Razor třídy modelu stránky Pages a třídy kontroleru MVC by se měly zaměřit na otázky uživatelského rozhraní.
Likvidace služeb
Kontejner volá Dispose typy, které IDisposable vytvoří. Služby vyřešené z kontejneru by nikdy neměly být uvolněny vývojářem. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.
V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky uvolněny: 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");
}
}
Po každé aktualizaci stránky indexu se v konzole ladění zobrazí následující výstup:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
Služby, které se nevytvořily kontejnerem služby
Uvažujte následující kód:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
V předchozím kódu:
- Instance služby se nevytvořily kontejnerem služby.
- Architektura nelikviduje služby automaticky.
- Vývojář zodpovídá za likvidaci služeb.
Pokyny pro IDisposable pro přechodné a sdílené instance
Viz pokyny pro IDisposable pro přechodné a sdílené instance v injektáži závislostí v .NET.
Nahrazení výchozího kontejneru služby
Viz Nahrazení výchozího kontejneru služby v injektáži závislostí v .NET
Doporučení
Viz doporučení injektáž závislostí v .NET
Nepoužívejte vzor lokátoru služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci:
Nesprávně:
Správně:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Jinou variantou lokátoru služby, která se vyhne, je vložení továrny, která řeší závislosti za běhu. Obě tyto postupy se směšují s inverzí strategií řízení .
Vyhněte se statickému
HttpContext
přístupu (například IHttpContextAccessor.HttpContext).
Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.
Doporučené vzory pro víceklientské architektury v DI
Orchard Core je aplikační architektura pro vytváření modulárních aplikací s více tenanty na ASP.NET Core. Další informace najdete v dokumentaci k sadovému jádru.
Služby poskytované architekturou
Program.cs
zaregistruje služby, které aplikace používá, včetně funkcí platformy, jako jsou Entity Framework Core a ASP.NET Core MVC. Na začátku má poskytované Program.cs
služby definované architekturou v závislosti na tom, IServiceCollection
jak byl hostitel nakonfigurován. Pro aplikace založené na šablonách ASP.NET Core zaregistruje architektura více než 250 služeb.
Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:
Typ služby | Životnost |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Přechodná |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Přechodná |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Přechodná |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Přechodná |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Další materiály
- Injektáž závislostí do zobrazení v ASP.NET Core
- Injektáž závislostí do kontrolerů v ASP.NET Core
- Injektáž závislostí v obslužných rutinách požadavků v ASP.NET Core
- injektáž závislostí jádra Blazor ASP.NET
- Vzory konferencí NDC pro vývoj aplikací DI
- Spuštění aplikace v ASP.NET Core
- Aktivace middlewaru na základě objektu pro vytváření v ASP.NET Core
- Čtyři způsoby odstranění IDisposables v ASP.NET Core
- Psaní čistého kódu v ASP.NET jádru pomocí injektáže závislostí (MSDN)
- Explicitní závislosti – princip
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler)
- Postup registrace služby s více rozhraními v ASP.NET Core DI
Kirk Larkin, Steve Smith, Scott Addie a Brandon Dahler
ASP.NET Core podporuje vzor návrhu softwaru pro injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.
Další informace specifické pro injektáž závislostí v řadičích MVC najdete v tématu Injektáž závislostí do kontrolerů v ASP.NET Core.
Informace o použití injektáže závislostí v jiných aplikacích než ve webových aplikacích naleznete v tématu Injektáž závislostí v .NET.
Další informace o injektáži závislostí možností najdete v tématu Vzor možnosti v ASP.NET Core.
Toto téma obsahuje informace o injektáži závislostí v ASP.NET Core. Primární dokumentace k použití injektáže závislostí je obsažena v injektáži závislostí v .NET.
Zobrazení nebo stažení ukázkového kódu (postup stažení)
Přehled injektáže závislostí
Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MyDependency
třídu pomocí WriteMessage
metody, na které závisí jiné třídy:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Třída může vytvořit instanci MyDependency
třídy, aby využívala její WriteMessage
metodu. V následujícím příkladu MyDependency
je třída závislostí IndexModel
třídy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet created this message.");
}
}
Třída vytvoří a přímo závisí na MyDependency
třídě. Závislosti kódu, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:
- Chcete-li nahradit
MyDependency
jinou implementací,IndexModel
musí být třída změněna. - Pokud
MyDependency
obsahuje závislosti, musí je také nakonfigurovatIndexModel
třída. Ve velkém projektu s více třídami v závislosti naMyDependency
tom se konfigurační kód rozdělí v aplikaci. - Tato implementace je obtížná pro testování jednotek. Aplikace by měla používat napodobení nebo třídu zástupných procedur
MyDependency
, která s tímto přístupem není možná.
Injektáž závislostí řeší tyto problémy prostřednictvím:
- Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
- Registrace závislosti v kontejneru služby ASP.NET Core poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v metodě aplikace
Startup.ConfigureServices
. - Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.
V ukázkové aplikaciIMyDependency
rozhraní definuje metoduWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Toto rozhraní je implementováno konkrétním typem: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Ukázková aplikace zaregistruje IMyDependency
službu s konkrétním typem MyDependency
. Metoda AddScoped zaregistruje službu s vymezenou životností, životností jednoho požadavku. Životnosti služeb jsou popsány dále v tomto tématu.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddRazorPages();
}
V ukázkové aplikaci IMyDependency
se služba vyžádá a použije se k volání WriteMessage
metody:
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Pomocí vzoru DI kontroler:
- Nepoužívá konkrétní typ
MyDependency
, pouze rozhraní, kteréIMyDependency
implementuje. To usnadňuje změnu implementace, kterou kontroler používá beze změny kontroleru. - Nevytvoří instanci
MyDependency
, je vytvořená kontejnerem DI.
Implementaci rozhraní je možné vylepšit pomocí integrovaného IMyDependency
rozhraní API pro protokolování:
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}");
}
}
Aktualizovaná ConfigureServices
metoda zaregistruje novou IMyDependency
implementaci:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency2>();
services.AddRazorPages();
}
MyDependency2
závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>
je služba poskytovaná architekturou.
Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.
Kontejner se ILogger<TCategoryName>
vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.
V terminologii injektáže závislostí služba:
- Je obvykle objekt, který poskytuje službu jiným objektům, jako
IMyDependency
je služba. - Nesouvisí s webovou službou, i když služba může používat webovou službu.
Architektura poskytuje robustní systém protokolování . Implementace IMyDependency
uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které nevyžaduje registraci žádné služby: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);
}
}
Použití předchozího kódu není nutné aktualizovat ConfigureServices
, protože protokolování je poskytováno architekturou.
Služby vložené do spuštění
Služby lze vložit do Startup
konstruktoru Startup.Configure
a metody.
Při použití obecného hostitele () lze do konstruktoru Startup
vložit pouze následující služby:IHostBuilder
Do metody lze vložit jakoukoli službu zaregistrovanou v kontejneru Startup.Configure
DI:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
...
}
Další informace najdete v tématu Spuštění aplikace v konfiguraci ASP.NET Core a Access při spuštění.
Registrace skupin služeb pomocí rozšiřujících metod
Architektura ASP.NET Core používá konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME}
rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. AddControllers Například metoda rozšíření registruje služby vyžadované pro kontrolery MVC.
Následující kód je generován šablonou Razor Pages pomocí jednotlivých uživatelských účtů a ukazuje, jak přidat další služby do kontejneru pomocí rozšiřujících metod AddDbContext a 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();
}
Vezměme například následující metodu ConfigureServices
, která registruje služby a konfiguruje možnosti:
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();
}
Související skupiny registrací lze přesunout do rozšiřující metody pro registraci služeb. Například konfigurační služby jsou přidány do následující třídy:
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;
}
}
}
Ostatní služby jsou registrovány v podobné třídě. Následující metoda ConfigureServices
používá nové rozšiřující metody k registraci služeb:
public void ConfigureServices(IServiceCollection services)
{
services.AddConfig(Configuration)
.AddMyDependencyGroup();
services.AddRazorPages();
}
Poznámka Každá rozšiřující metoda services.Add{GROUP_NAME}
přidává a potenciálně konfiguruje služby. AddControllersWithViews například přidá kontrolery služeb MVC s požadavkem zobrazení a AddRazorPages přidá požadavky Razor Pages pro služby. Doporučujeme, aby aplikace dodržovaly zásady vytváření názvů pro vytváření rozšiřujících metod v oboru názvů Microsoft.Extensions.DependencyInjection. Vytváření rozšiřujících metod v oboru názvů Microsoft.Extensions.DependencyInjection
:
- Zapouzdřuje skupiny registrací služby.
- Poskytuje pohodlný přístup IntelliSense ke službě.
Životnost služeb
Zobrazení životnosti služby v injektáži závislostí v .NET
Pokud chcete v middlewaru používat omezené služby, použijte jeden z následujících přístupů:
- Vložte službu do middlewaru
Invoke
neboInvokeAsync
metody. Použití injektáže konstruktoru vyvolá výjimku modulu runtime, protože vynutí, aby se vymezená služba chovala jako singleton. Ukázka v části Možnosti životnosti a registrace ukazujeInvokeAsync
přístup. - Použijte middleware založený na továrně. Middleware zaregistrovaný pomocí tohoto přístupu se aktivuje podle požadavku klienta (připojení), což umožňuje vkládání vymezených služeb do metody middlewaru
InvokeAsync
.
Další informace najdete v tématu Psaní vlastního middlewaru ASP.NET Core.
Metody registrace služby
Viz Metody registrace služby v injektáži závislostí v .NET
Při testování je běžné používat více implementací.
Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .
K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton
se jako typ služby volá dvakrát IMyDependency
. Druhé volání, které AddSingleton
přepíše předchozí, když je vyřešeno jako IMyDependency
a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMyDependency>
. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím 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);
}
}
Chování injektáže konstruktoru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Kontexty Entity Frameworku
Ve výchozím nastavení se kontexty Entity Framework přidají do kontejneru služby pomocí doby života s vymezeným oborem, protože databázové operace webové aplikace jsou obvykle vymezeny na požadavek klienta. Pokud chcete použít jinou životnost, zadejte životnost pomocí AddDbContext přetížení. Služby daného životního cyklu by neměly používat kontext databáze s dobou života kratší než životnost služby.
Možnosti životnosti a registrace
Abychom si ukázali rozdíl mezi životnostmi služeb a možnostmi jejich registrace, zvažte následující rozhraní, která představují úlohu jako operaci s identifikátorem. OperationId
V závislosti na tom, jak je pro následující rozhraní nakonfigurovaná doba života služby operace, kontejner při vyžádání třídou poskytuje buď stejné, nebo různé instance služby:
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Následující Operation
třída implementuje všechna předchozí rozhraní. Konstruktor Operation
vygeneruje identifikátor GUID a uloží poslední 4 znaky ve OperationId
vlastnosti:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Metoda Startup.ConfigureServices
vytvoří více registrací Operation
třídy podle pojmenovaných životností:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddRazorPages();
}
Ukázková aplikace ukazuje životnost objektů v rámci požadavků i mezi požadavky. Middleware IndexModel
a požadavek na každý typ IOperation
a protokolovat OperationId
pro každý typ:
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);
}
}
IndexModel
Podobně jako middleware řeší stejné služby:
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>();
}
}
Služby s vymezeným oborem InvokeAsync
musí být vyřešeny v metodě:
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);
}
Výstup protokolovacího modulu ukazuje:
- Přechodné objekty se vždy liší. Přechodná
OperationId
hodnota se v middlewaruIndexModel
a v prostředním prostředí liší. - Objekty s vymezeným oborem jsou pro daný požadavek stejné, ale liší se v rámci každého nového požadavku.
- Jednoúčelové objekty jsou pro každý požadavek stejné.
Pokud chcete snížit výstup protokolování, nastavte v appsettings.Development.json
souboru "Logging:LogLevel:Microsoft:Error":
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Volání služeb z hlavního
Vytvořte s IServiceScope IServiceScopeFactory.CreateScope k vyřešení vymezené služby v rámci oboru aplikace. Tento přístup je užitečný pro přístup ke službě s vymezeným oborem při spuštění, aby bylo možné spouštět úlohy inicializace.
Následující příklad ukazuje, jak přistupovat k vymezené IMyDependency
službě a volat její WriteMessage
metodu v 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>();
});
}
Ověření oboru
Viz Chování injektáže konstruktoru v injektáži závislostí v .NET
Další informace najdete v tématu Ověření oboru.
Vyžádat služby
Služby a jejich závislosti v rámci požadavku ASP.NET Core jsou zpřístupněny prostřednictvím HttpContext.RequestServices.
Architektura vytvoří obor na požadavek a RequestServices
zpřístupňuje vymezeného poskytovatele služeb. Všechny služby s vymezeným oborem jsou platné, pokud je požadavek aktivní.
Poznámka:
Upřednostňujte vyžádání závislostí jako parametrů konstruktoru před překladem služeb z RequestServices
. Vyžádání závislostí jako parametrů konstruktoru poskytuje třídy, které jsou snadnější testovat.
Návrh služeb pro injektáž závislostí
Při navrhování služeb pro injektáž závislostí:
- Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
- Vyhněte se přímé instanci závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
- Zpřístupnit služby malými, dobře faktorovanými a snadno otestovanými.
Pokud má třída velké množství vloženého závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu SRP (Single Responsibility Principle). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd. Mějte na paměti, že Razor třídy modelu stránky Pages a třídy kontroleru MVC by se měly zaměřit na otázky uživatelského rozhraní.
Likvidace služeb
Kontejner volá Dispose typy, které IDisposable vytvoří. Služby vyřešené z kontejneru by nikdy neměly být uvolněny vývojářem. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.
V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky odstraněny:
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");
}
}
Po každé aktualizaci stránky indexu se v konzole ladění zobrazí následující výstup:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose
Služby, které se nevytvořily kontejnerem služby
Uvažujte následující kód:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new Service1());
services.AddSingleton(new Service2());
services.AddRazorPages();
}
V předchozím kódu:
- Instance služby se nevytvořily kontejnerem služby.
- Architektura nelikviduje služby automaticky.
- Vývojář zodpovídá za likvidaci služeb.
Pokyny pro IDisposable pro přechodné a sdílené instance
Viz pokyny pro IDisposable pro přechodné a sdílené instance v injektáži závislostí v .NET.
Nahrazení výchozího kontejneru služby
Viz Nahrazení výchozího kontejneru služby v injektáži závislostí v .NET
Doporučení
Viz doporučení injektáž závislostí v .NET
Nepoužívejte vzor lokátoru služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci:
Nesprávně:
Správně:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Jinou variantou lokátoru služby, která se vyhne, je vložení továrny, která řeší závislosti za běhu. Obě tyto postupy se směšují s inverzí strategií řízení .
Vyhněte se statickému
HttpContext
přístupu (například IHttpContextAccessor.HttpContext).
Vyhněte se volání do BuildServiceProvider
ConfigureServices
. VoláníBuildServiceProvider
obvykle nastane, když vývojář chce vyřešit službu vConfigureServices
. Představte si například případLoginPath
načtení z konfigurace. Vyhněte se následujícímu přístupu:Na předchozím obrázku se po výběru zelené vlnovky pod
services.BuildServiceProvider
obrázkem zobrazí následující ASP0000 upozornění:ASP0000 volání BuildServiceProvider z kódu aplikace vede k vytvoření další kopie jednoúčelových služeb. Zvažte alternativy, jako je například vkládání závislostí, jako jsou parametry konfigurace.
Volání
BuildServiceProvider
vytvoří druhý kontejner, který může vytvořit roztrhané jednotony a způsobit odkazy na grafy objektů napříč více kontejnery.Správným způsobem, jak získat
LoginPath
, je použít integrovanou podporu distančního modelu možností: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(); }
Jednorázové přechodné služby jsou zachyceny kontejnerem pro odstranění. To se může změnit na nevracení paměti, pokud je vyřešeno z kontejneru nejvyšší úrovně.
Povolte ověřování oboru, abyste měli jistotu, že aplikace nemá jednotony, které zachycují vymezené služby. Další informace najdete v tématu Ověření oboru.
Stejně jako u všech sad doporučení můžete narazit na situace, kdy se vyžaduje ignorování doporučení. Výjimky jsou vzácné, většinou zvláštní případy v rámci samotné architektury.
Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.
Doporučené vzory pro víceklientské architektury v DI
Orchard Core je aplikační architektura pro vytváření modulárních aplikací s více tenanty na ASP.NET Core. Další informace najdete v dokumentaci k sadovému jádru.
Služby poskytované architekturou
Metoda Startup.ConfigureServices
registruje služby, které aplikace používá, včetně funkcí platformy, jako jsou Entity Framework Core a ASP.NET Core MVC. Na začátku má poskytované ConfigureServices
služby definované architekturou v závislosti na tom, IServiceCollection
jak byl hostitel nakonfigurován. Pro aplikace založené na šablonách ASP.NET Core zaregistruje architektura více než 250 služeb.
Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:
Typ služby | Životnost |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Přechodná |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Přechodná |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Přechodná |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Přechodná |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Další materiály
- Injektáž závislostí do zobrazení v ASP.NET Core
- Injektáž závislostí do kontrolerů v ASP.NET Core
- Injektáž závislostí v obslužných rutinách požadavků v ASP.NET Core
- injektáž závislostí jádra Blazor ASP.NET
- Vzory konferencí NDC pro vývoj aplikací DI
- Spuštění aplikace v ASP.NET Core
- Aktivace middlewaru na základě objektu pro vytváření v ASP.NET Core
- Čtyři způsoby odstranění IDisposables v ASP.NET Core
- Psaní čistého kódu v ASP.NET jádru pomocí injektáže závislostí (MSDN)
- Explicitní závislosti – princip
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler)
- Postup registrace služby s více rozhraními v ASP.NET Core DI