Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga / Notatka
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, przeczytaj artykuł w wersji .NET 9.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, przeczytaj artykuł w wersji .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, wyraźnych ani domniemanych, w odniesieniu do podanych tutaj informacji.
Aby zapoznać się z bieżącą wersją, przeczytaj artykuł w wersji .NET 9.
Przez Kirk Larkin, Steve Smith i Brandon Dahler
ASP.NET Core obsługuje wzorzec projektowania oprogramowania iniekcji zależności (DI), który jest techniką osiągnięcia inwersji kontroli (IoC) między klasami i ich zależnościami.
Aby uzyskać Blazor wskazówki dotyczące DI, które dodają lub zastępują wytyczne w tym artykule, zobacz ASP.NET Core Blazor zależności iniekcji.
Aby uzyskać informacje specyficzne dla wstrzykiwania zależności w kontrolerach MVC, zobacz Wstrzykiwanie zależności do kontrolerów w ASP.NET Core.
Aby uzyskać informacje na temat używania wstrzykiwania zależności w aplikacjach innych niż aplikacje internetowe, zobacz Wstrzykiwanie zależności na platformie .NET.
Aby uzyskać informacje na temat wstrzykiwania zależności opcji, zobacz wzorzec opcji w ASP.NET Core.
Ten artykuł zawiera informacje na temat wstrzykiwania zależności w programie ASP.NET Core. Podstawowa dokumentacja dotycząca używania wstrzykiwania zależności znajduje się w Wstrzykiwanie zależności w .NET.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Omówienie wstrzykiwania zależności
Zależność to obiekt, od którego zależy inny obiekt. Zbadaj następującą klasę MyDependency
zawierającą metodę WriteMessage
, od której zależą inne klasy.
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Klasa może utworzyć wystąpienie MyDependency
klasy w celu użycia jej WriteMessage
metody. W poniższym przykładzie MyDependency
klasa jest zależnością IndexModel
klasy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Klasa tworzy i bezpośrednio zależy od klasy MyDependency
. Zależności kodu, takie jak w poprzednim przykładzie, są problematyczne i należy unikać z następujących powodów:
- Aby zastąpić
MyDependency
inną implementacją, należy zmodyfikować klasęIndexModel
. - Jeśli
MyDependency
ma zależności, muszą być również skonfigurowane przez klasęIndexModel
. W dużym projekcie z wieloma klasami zależnymi odMyDependency
kod konfiguracji staje się rozpraszany po całej aplikacji. - Ta implementacja jest trudna do testowania jednostkowego.
Wstrzykiwanie zależności rozwiązuje te problemy przez:
- Użycie interfejsu lub klasy bazowej do abstrakcji implementacji zależności.
- Rejestracja zależności w kontenerze usługi. ASP.NET Core udostępnia wbudowany kontener usługi. IServiceProvider Usługi są zwykle rejestrowane w pliku aplikacji
Program.cs
. - Iniekcja usługi do konstruktora klasy, w której jest używana. Struktura bierze na siebie odpowiedzialność za utworzenie wystąpienia zależności i usunięcie go, gdy nie jest już potrzebne.
W przykładowej aplikacjiIMyDependency
interfejs definiuje metodęWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Ten interfejs jest implementowany przez konkretny typ: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Przykładowa aplikacja rejestruje usługę IMyDependency
przy użyciu konkretnego typu MyDependency
. Metoda AddScoped rejestruje usługę z okresem istnienia o określonym zakresie, okres istnienia pojedynczego żądania.
okresy istnienia usługi zostały opisane w dalszej części tego artykułu.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
W aplikacji przykładowej usługa IMyDependency
jest wykorzystywana do żądania i wywoływania metody WriteMessage
.
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Korzystając ze wzorca DI, kontroler lub Razor strona:
- Nie używa konkretnego typu
MyDependency
, tylko interfejsuIMyDependency
, który implementuje. Ułatwia to zmianę implementacji bez modyfikowania kontrolera lub Razor strony. - Nie tworzy wystąpienia klasy
MyDependency
, które jest tworzone przez kontener DI.
Implementację interfejsu IMyDependency
można ulepszyć przy użyciu wbudowanego interfejsu API rejestrowania:
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}");
}
}
Zaktualizowana Program.cs
wersja rejestruje nową IMyDependency
implementację:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
zależy od ILogger<TCategoryName>, który jest żądany w konstruktorze.
ILogger<TCategoryName>
jest usługą zapewnianą przez platformę.
Nie jest niczym niezwykłym używanie wstrzykiwania zależności w sposób łańcuchowy. Każda żądana zależność z kolei żąda własnych zależności. Kontener rozpoznaje zależności na grafie i zwraca w pełni rozwiązaną usługę. Zbiorczy zestaw zależności, które należy rozpoznać, jest zwykle określany jako drzewo zależności, graf zależności lub graf obiektu.
Kontener rozwiązuje problem ILogger<TCategoryName>
dzięki wykorzystaniu (ogólnych) typów otwartych, eliminując konieczność rejestrowania każdego typu skonstruowanego (ogólnego).
W terminologii iniekcji zależności usługa:
- Jest to zazwyczaj obiekt, który zapewnia usługę innym obiektom, takim jak
IMyDependency
usługa. - Nie jest powiązana z usługą internetową, chociaż usługa może używać usługi internetowej.
Platforma zapewnia niezawodny system rejestrowania . Implementacje IMyDependency
pokazane w poprzednich przykładach zostały napisane, aby zademonstrować podstawowe di, a nie zaimplementować rejestrowania. Większość aplikacji nie powinna tworzyć rejestratorów. Poniższy kod demonstruje użycie domyślnego rejestrowania, które nie wymaga zarejestrowania żadnych usług:
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);
}
}
Powyższy kod działa poprawnie bez zmiany niczego w Program.cs
, ponieważ rejestrowanie jest zapewniane przez framework.
Rejestrowanie grup usług za pomocą metod rozszerzeń
Platforma ASP.NET Core używa konwencji do rejestrowania grupy powiązanych usług. Konwencja polega na użyciu jednej Add{GROUP_NAME}
metody rozszerzenia do rejestrowania wszystkich usług wymaganych przez funkcję platformy. Na przykład AddControllers metoda rozszerzenia rejestruje usługi wymagane dla kontrolerów MVC.
Poniższy kod jest generowany przez szablon Razor Pages, stosując indywidualne konta, i pokazuje, jak dodać dodatkowe usługi do kontenera przy użyciu metod rozszerzeń AddDbContext i 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();
Rozważ następujące kwestie, które rejestrują usługi i konfigurują opcje:
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();
Powiązane grupy rejestracji można przenieść do metody rozszerzenia w celu zarejestrowania usług. Na przykład usługi konfiguracji są dodawane do następującej klasy:
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;
}
}
}
Pozostałe usługi są rejestrowane w podobnej klasie. Poniższy kod używa nowych metod rozszerzenia do rejestrowania usług:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Uwaga: Każda services.Add{GROUP_NAME}
metoda rozszerzenia dodaje i potencjalnie konfiguruje usługi. Na przykład AddControllersWithViews dodaje do usług kontrolery MVC, które wymagają widoków, a AddRazorPages dodaje usługi wymagane przez Razor Pages.
Okresy istnienia usługi
Zobacz Czas życia usług w iniekcji zależności na platformie .NET
Aby użyć usług o określonym zakresie w programie pośredniczącym, użyj jednej z następujących metod:
- Wstrzyknij usługę do metody
Invoke
lubInvokeAsync
oprogramowania pośredniczącego. Użycie iniekcji konstruktora zgłasza wyjątek w czasie wykonywania, ponieważ wymusza zachowanie usługi o określonym zakresie jak singleton. Przykład w sekcji Opcje okresu istnienia i rejestracji demonstruje podejścieInvokeAsync
. - Użyj oprogramowania pośredniczącego opartego na fabryce. Oprogramowanie pośredniczące zarejestrowane przy użyciu tego podejścia jest aktywowane na żądanie klienta (połączenie), które umożliwia wstrzyknięcie usług o określonym zakresie do konstruktora oprogramowania pośredniczącego.
Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowego oprogramowania pośredniczącego ASP.NET Core.
Metody rejestracji usługi
Zobacz Metody rejestracji usług w wstrzykiwaniu zależności na platformie .NET
Często używa się wielu implementacji podczas mockowania typów do testowania.
Zarejestrowanie usługi tylko z typem implementacji jest równoważne zarejestrowaniu tej usługi z tą samą implementacją i typem usługi. Dlatego nie można zarejestrować wielu implementacji usługi przy użyciu metod, które nie przyjmują jawnego typu usługi. Te metody mogą rejestrować wiele wystąpień usługi, ale wszystkie mają ten sam typ implementacji.
Każda z tych metod rejestracji usług może służyć do rejestrowania wielu wystąpień usługi tego samego typu usługi. W poniższym przykładzie AddSingleton
jest wywoływana dwukrotnie z IMyDependency
typem usługi. Drugie wywołanie AddSingleton
przesłania poprzednie, gdy jest rozwiązane jako IMyDependency
i dodaje do poprzedniego, gdy wiele usług jest rozwiązanych poprzez IEnumerable<IMyDependency>
. Usługi są wyświetlane w kolejności, w której zostały zarejestrowane, gdy są rozwiązywane za pośrednictwem 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);
}
}
Usługi związane z kluczami
Termin usługi z kluczami odnosi się do mechanizmu rejestrowania i pobierania usług Dependency Injection (DI) przy użyciu kluczy. Usługa jest skojarzona z kluczem przez wywołanie AddKeyedSingleton (lub AddKeyedScoped
lub AddKeyedTransient
) w celu jej zarejestrowania. Uzyskaj dostęp do zarejestrowanej usługi, określając klucz za pomocą atrybutu [FromKeyedServices]
. Poniższy kod pokazuje, jak używać usług z przypisanymi kluczami:
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"));
}
}
Usługi kluczowane w oprogramowaniu pośredniczącym
Oprogramowanie pośredniczące obsługuje usługi kluczowe zarówno w konstruktorze, jak i w metodzie Invoke
/InvokeAsync
.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");
var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();
internal class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next,
[FromKeyedServices("test")] MySingletonClass service)
{
_next = next;
}
public Task Invoke(HttpContext context,
[FromKeyedServices("test2")]
MyScopedClass scopedService) => _next(context);
}
Aby uzyskać więcej informacji na temat tworzenia oprogramowania pośredniczącego, zobacz Pisanie niestandardowego oprogramowania pośredniczącego ASP.NET Core
Zachowanie iniekcji konstruktora
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Konteksty programu Entity Framework
Domyślnie konteksty programu Entity Framework są dodawane do kontenera usługi przy użyciu okresu istnienia o określonym zakresie , ponieważ operacje bazy danych aplikacji internetowej są zwykle ograniczone do żądania klienta. Aby użyć innej żywotności, określ ją przy użyciu przeciążenia AddDbContext. Usługi danego okresu istnienia nie powinny używać kontekstu bazy danych z okresem istnienia krótszym niż okres istnienia usługi.
Opcje okresu istnienia i rejestracji
Aby zademonstrować różnicę między okresami istnienia usługi a ich opcjami rejestracji, należy wziąć pod uwagę następujące interfejsy reprezentujące zadanie jako operację z identyfikatorem OperationId
. W zależności od tego, jak cykl życia usługi operacyjnej jest skonfigurowany dla następujących interfejsów, kontener udostępnia te same lub różne instancje usługi na żądanie klasy.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Poniższa Operation
klasa implementuje wszystkie poprzednie interfejsy. Konstruktor Operation
generuje identyfikator GUID i przechowuje ostatnie 4 znaki we OperationId
właściwości :
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Poniższy kod tworzy wiele rejestracji klasy Operation
zgodnie z nazwanymi okresami istnienia.
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();
Przykładowa aplikacja demonstruje okresy istnienia obiektów zarówno wewnątrz, jak i między żądaniami.
IndexModel
i oprogramowanie pośredniczące żądają każdego rodzaju typu IOperation
i rejestrują OperationId
dla każdego:
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);
}
}
Podobnie jak w przypadku IndexModel
, oprogramowanie pośredniczące rozwiązuje te same usługi.
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>();
}
}
Usługi o zasięgu lokalnym i tymczasowym muszą zostać rozwiązane w metodzie InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Dane wyjściowe rejestratora pokazują:
-
Obiekty przejściowe są zawsze różne. Wartość przejściowa
OperationId
różni się wIndexModel
i w oprogramowaniu pośredniczącym. - Obiekty o określonym zakresie są takie same dla danego żądania, ale różnią się w zależności od każdego nowego żądania.
- Pojedyncze obiekty są takie same dla każdego żądania.
Aby zmniejszyć dane wyjściowe rejestrowania, ustaw wartość "Logging:LogLevel:Microsoft:Error" w pliku appsettings.Development.json
.
{
"MyKey": "MyKey from appsettings.Development.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Rozwiązywanie usługi podczas uruchamiania aplikacji
Poniższy kod pokazuje, jak rozwiązać problem z usługą o określonym zakresie przez ograniczony czas trwania podczas uruchamiania aplikacji:
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();
Walidacja zakresu
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Aby uzyskać więcej informacji, zobacz Walidacja zakresu.
Zamów usługi
Usługi i ich zależności w ramach żądania ASP.NET Core są udostępniane za pośrednictwem usługi HttpContext.RequestServices.
Platforma tworzy zakres na żądanie i RequestServices
uwidacznia dostawcę usług o określonym zakresie. Wszystkie usługi o określonym zakresie są prawidłowe tak długo, jak żądanie jest aktywne.
Uwaga / Notatka
Zaleca się żądanie zależności jako parametrów konstruktora zamiast rozwiązywać usługi z RequestServices
. Żądanie zależności jako parametrów konstruktora daje klasy, które są łatwiejsze do przetestowania.
Usługi projektowe do wstrzykiwania zależności
Podczas projektowania usług do wstrzykiwania zależności:
- Unikaj stanowych, statycznych klas i składowych. Unikaj tworzenia stanu globalnego, projektując aplikacje, które korzystają z usług singletonów.
- Unikaj, bezpośredniego tworzenia wystąpień klas zależnych w ramach usług. Bezpośrednie tworzenie instancji wiąże kod z konkretną implementacją.
- Twórz usługi małe, dobrze zaprojektowane i łatwe do testowania.
Jeśli klasa ma wiele wstrzykiwanych zależności, może to być znak, że klasa ma zbyt wiele obowiązków i narusza zasadę pojedynczej odpowiedzialności (SRP). Spróbuj refaktoryzować klasę, przenosząc część swoich obowiązków do nowych klas. Należy pamiętać, że Razor klasy modeli stron i klasy kontrolerów MVC powinny skupić się na kwestiach związanych z interfejsem użytkownika.
Likwidacja usług
Kontener wywołuje Dispose dla typów IDisposable, które tworzy. Usługi pobrane z kontenera nigdy nie powinny być likwidowane przez dewelopera. Jeśli typ lub fabryka jest zarejestrowany jako singleton, kontener automatycznie usuwa singleton.
W poniższym przykładzie usługi są tworzone przez kontener usługi i usuwane automatycznie:
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");
}
}
Konsola debugowania wyświetla następujące dane wyjściowe po każdym odświeżeniu strony Indeks:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Development.json
Service1.Dispose
Usługi nieutworowane przez kontener usługi
Rozważ następujący kod:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
W poprzednim kodzie:
- Wystąpienia usługi nie są tworzone przez kontener usługi.
- Platforma nie usuwa automatycznie usług.
- Deweloper jest odpowiedzialny za likwidowanie usług.
Wskazówki dotyczące interfejsu IDisposable dla wystąpień przejściowych i udostępnionych
Zobacz wskazówki dotyczące funkcji IDisposable dla przejściowej i współdzielonej instancji w wstrzykiwaniu zależności na platformie .NET.
Zastąpienie domyślnego kontenera usług
Zobacz Domyślne zastępowanie kontenerów usług w iniekcji zależności w .NET
Rekomendacje
Zobacz Zalecenia dotyczące wstrzykiwania zależności na platformie .NET
Unikaj używania wzorca lokalizatora usług. Na przykład nie należy wywoływać GetService w celu uzyskania instancji usługi, gdy można użyć DI:
niepoprawne:
Odpowiedź prawidłowa:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Unikaj innej odmiany lokalizatora usług polegającej na wstrzykiwaniu fabryki, która rozwiązuje zależności w czasie wykonywania. Obie te praktyki mieszają strategie Odwrócenia Kontroli.
Unikaj dostępu statycznego do
HttpContext
(na przykład IHttpContextAccessor.HttpContext).
DI to alternatywa dla wzorców dostępu do obiektów statycznych/globalnych. Możesz nie być w stanie w pełni skorzystać z zalet DI, jeśli połączysz go z dostępem do obiektów statycznych.
Zalecane schematy dla wielościeżnikowości w DI
Orchard Core to struktura aplikacji do tworzenia modułowych, wielodostępnych aplikacji na platformie ASP.NET Core. Aby uzyskać więcej informacji, zobacz dokumentację Orchard Core.
Zobacz przykłady Orchard Core, aby dowiedzieć się, jak tworzyć modułowe i wielodostępowe aplikacje, korzystając z Orchard Core Framework bez funkcji specyficznych dla CMS.
Usługi dostarczane przez platformę
Program.cs
rejestruje usługi używane przez aplikację, w tym funkcje platformy, takie jak Entity Framework Core i ASP.NET Core MVC. Początkowo IServiceCollection
, dostarczany do Program.cs
, zawiera usługi zdefiniowane przez framework, w zależności od tego, w jaki sposób host został skonfigurowany. W przypadku aplikacji opartych na szablonach ASP.NET Core platforma rejestruje ponad 250 usług.
W poniższej tabeli wymieniono niewielki zbiór tych usług zarejestrowanych w ramach systemu:
Typ usługi | Okres istnienia |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Przemijający |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Przemijający |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Przemijający |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Przemijający |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Dodatkowe zasoby
- iniekcja zależności ASP.NET Core Blazor
- Wstrzykiwanie zależności do widoków w programie ASP.NET Core
- Wstrzykiwanie zależności do kontrolerów w ASP.NET Core
- Wstrzykiwanie zależności w obsługiwaczach wymagań w ASP.NET Core
- Wzorce konferencji NDC na potrzeby tworzenia aplikacji di
- Uruchamianie aplikacji na platformie ASP.NET Core
- Aktywacja oprogramowania pośredniczącego opartego na fabryce na platformie ASP.NET Core
- Omówienie podstaw iniekcji zależności na platformie .NET
- Wskazówki dotyczące wstrzykiwania zależności
- Samouczek: używanie wstrzykiwania zależności na platformie .NET
- Wstrzykiwanie zależności platformy .NET
- ASP.NET Core WSTRZYKIWANIE ZALEŻNOŚCI: CO TO JEST ISERVICECOLLECTION?
- Cztery sposoby usuwania obiektów IDisposable w środowisku ASP.NET Core
- Pisanie czystego kodu w ASP.NET Core za pomocą wstrzykiwania zależności (MSDN)
- Jawna zasada zależności
- Kontenery inwersji sterowania i wzorzec wstrzykiwania zależności (Martin Fowler)
- Jak zarejestrować usługę za pomocą wielu interfejsów w usłudze ASP.NET Core DI
Przez Kirk Larkin, Steve Smith i Brandon Dahler
ASP.NET Core obsługuje wzorzec projektowania oprogramowania iniekcji zależności (DI), który jest techniką osiągnięcia inwersji kontroli (IoC) między klasami i ich zależnościami.
Aby uzyskać więcej informacji specyficznych dla wstrzykiwania zależności w kontrolerach MVC, zobacz Wstrzykiwanie zależności do kontrolerów w ASP.NET Core.
Aby uzyskać informacje na temat używania wstrzykiwania zależności w aplikacjach innych niż aplikacje internetowe, zobacz Wstrzykiwanie zależności na platformie .NET.
Aby uzyskać więcej informacji na temat wstrzykiwania zależności opcji, zobacz Wzorzec opcji w ASP.NET Core.
Ten temat zawiera informacje na temat wstrzykiwania zależności w programie ASP.NET Core. Podstawowa dokumentacja dotycząca używania wstrzykiwania zależności znajduje się w Wstrzykiwanie zależności w .NET.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Omówienie wstrzykiwania zależności
Zależność to obiekt, od którego zależy inny obiekt. Zbadaj następującą klasę MyDependency
zawierającą metodę WriteMessage
, od której zależą inne klasy.
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Klasa może utworzyć wystąpienie MyDependency
klasy w celu użycia jej WriteMessage
metody. W poniższym przykładzie MyDependency
klasa jest zależnością IndexModel
klasy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Klasa tworzy i bezpośrednio zależy od klasy MyDependency
. Zależności kodu, takie jak w poprzednim przykładzie, są problematyczne i należy unikać z następujących powodów:
- Aby zastąpić
MyDependency
inną implementacją, należy zmodyfikować klasęIndexModel
. - Jeśli
MyDependency
ma zależności, muszą być również skonfigurowane przez klasęIndexModel
. W dużym projekcie z wieloma klasami zależnymi odMyDependency
kod konfiguracji staje się rozpraszany po całej aplikacji. - Ta implementacja jest trudna do testowania jednostkowego.
Wstrzykiwanie zależności rozwiązuje te problemy przez:
- Użycie interfejsu lub klasy bazowej do abstrakcji implementacji zależności.
- Rejestracja zależności w kontenerze usługi. ASP.NET Core udostępnia wbudowany kontener usługi. IServiceProvider Usługi są zwykle rejestrowane w pliku aplikacji
Program.cs
. - Iniekcja usługi do konstruktora klasy, w której jest używana. Struktura bierze na siebie odpowiedzialność za utworzenie wystąpienia zależności i usunięcie go, gdy nie jest już potrzebne.
W przykładowej aplikacjiIMyDependency
interfejs definiuje metodęWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Ten interfejs jest implementowany przez konkretny typ: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Przykładowa aplikacja rejestruje usługę IMyDependency
przy użyciu konkretnego typu MyDependency
. Metoda AddScoped rejestruje usługę z okresem istnienia o określonym zakresie, okres istnienia pojedynczego żądania.
Cykle życia usługi opisano w dalszej części tego tematu.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
W aplikacji przykładowej usługa IMyDependency
jest wykorzystywana do żądania i wywoływania metody WriteMessage
.
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Korzystając ze wzorca DI, kontroler lub Razor strona:
- Nie używa konkretnego typu
MyDependency
, tylko interfejsuIMyDependency
, który implementuje. Ułatwia to zmianę implementacji bez modyfikowania kontrolera lub Razor strony. - Nie tworzy wystąpienia klasy
MyDependency
, które jest tworzone przez kontener DI.
Implementację interfejsu IMyDependency
można ulepszyć przy użyciu wbudowanego interfejsu API rejestrowania:
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}");
}
}
Zaktualizowana Program.cs
wersja rejestruje nową IMyDependency
implementację:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
zależy od ILogger<TCategoryName>, który jest żądany w konstruktorze.
ILogger<TCategoryName>
jest usługą zapewnianą przez platformę.
Nie jest niczym niezwykłym używanie wstrzykiwania zależności w sposób łańcuchowy. Każda żądana zależność z kolei żąda własnych zależności. Kontener rozpoznaje zależności na grafie i zwraca w pełni rozwiązaną usługę. Zbiorczy zestaw zależności, które należy rozpoznać, jest zwykle określany jako drzewo zależności, graf zależności lub graf obiektu.
Kontener rozwiązuje problem ILogger<TCategoryName>
dzięki wykorzystaniu (ogólnych) typów otwartych, eliminując konieczność rejestrowania każdego typu skonstruowanego (ogólnego).
W terminologii iniekcji zależności usługa:
- Jest to zazwyczaj obiekt, który zapewnia usługę innym obiektom, takim jak
IMyDependency
usługa. - Nie jest powiązana z usługą internetową, chociaż usługa może używać usługi internetowej.
Platforma zapewnia niezawodny system rejestrowania . Implementacje IMyDependency
pokazane w poprzednich przykładach zostały napisane, aby zademonstrować podstawowe di, a nie zaimplementować rejestrowania. Większość aplikacji nie powinna tworzyć rejestratorów. Poniższy kod demonstruje użycie domyślnego rejestrowania, które nie wymaga zarejestrowania żadnych usług:
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);
}
}
Korzystając z powyższego kodu, nie ma potrzeby aktualizowania Program.cs
elementu , ponieważ rejestrowanie jest udostępniane przez platformę.
Rejestrowanie grup usług za pomocą metod rozszerzeń
Platforma ASP.NET Core używa konwencji do rejestrowania grupy powiązanych usług. Konwencja polega na użyciu jednej Add{GROUP_NAME}
metody rozszerzenia do rejestrowania wszystkich usług wymaganych przez funkcję platformy. Na przykład AddControllers metoda rozszerzenia rejestruje usługi wymagane dla kontrolerów MVC.
Poniższy kod jest generowany przez szablon Razor Pages, stosując indywidualne konta, i pokazuje, jak dodać dodatkowe usługi do kontenera przy użyciu metod rozszerzeń AddDbContext i 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();
Rozważ następujące kwestie, które rejestrują usługi i konfigurują opcje:
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();
Powiązane grupy rejestracji można przenieść do metody rozszerzenia w celu zarejestrowania usług. Na przykład usługi konfiguracji są dodawane do następującej klasy:
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;
}
}
}
Pozostałe usługi są rejestrowane w podobnej klasie. Poniższy kod używa nowych metod rozszerzenia do rejestrowania usług:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Uwaga: Każda services.Add{GROUP_NAME}
metoda rozszerzenia dodaje i potencjalnie konfiguruje usługi. Na przykład AddControllersWithViews dodaje do usług kontrolery MVC, które wymagają widoków, a AddRazorPages dodaje usługi wymagane przez Razor Pages.
Okresy istnienia usługi
Zobacz Czas życia usług w iniekcji zależności na platformie .NET
Aby użyć usług o określonym zakresie w programie pośredniczącym, użyj jednej z następujących metod:
- Wstrzyknij usługę do metody
Invoke
lubInvokeAsync
oprogramowania pośredniczącego. Użycie iniekcji konstruktora zgłasza wyjątek w czasie wykonywania, ponieważ wymusza zachowanie usługi o określonym zakresie jak singleton. Przykład w sekcji Opcje okresu istnienia i rejestracji demonstruje podejścieInvokeAsync
. - Użyj oprogramowania pośredniczącego opartego na fabryce. Oprogramowanie pośredniczące zarejestrowane przy użyciu tego podejścia jest aktywowane na żądanie klienta (połączenie), które umożliwia wstrzyknięcie usług o określonym zakresie do konstruktora oprogramowania pośredniczącego.
Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowego oprogramowania pośredniczącego ASP.NET Core.
Metody rejestracji usługi
Zobacz Metody rejestracji usług w wstrzykiwaniu zależności na platformie .NET
Często używa się wielu implementacji podczas mockowania typów do testowania.
Zarejestrowanie usługi tylko z typem implementacji jest równoważne zarejestrowaniu tej usługi z tą samą implementacją i typem usługi. Dlatego nie można zarejestrować wielu implementacji usługi przy użyciu metod, które nie przyjmują jawnego typu usługi. Te metody mogą rejestrować wiele wystąpień usługi, ale wszystkie te metody będą miały ten sam typ implementacji .
Każda z powyższych metod rejestracji usług może służyć do rejestrowania wielu wystąpień usługi tego samego typu usługi. W poniższym przykładzie AddSingleton
jest wywoływana dwukrotnie z IMyDependency
typem usługi. Drugie wywołanie AddSingleton
przesłania poprzednie, gdy jest rozwiązane jako IMyDependency
i dodaje do poprzedniego, gdy wiele usług jest rozwiązanych poprzez IEnumerable<IMyDependency>
. Usługi są wyświetlane w kolejności, w której zostały zarejestrowane, gdy są rozwiązywane za pośrednictwem 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);
}
}
Usługi związane z kluczami
Serwisy kluczowe odnosi się do mechanizmu rejestrowania i pobierania serwisów Dependency Injection (DI) opartych na kluczu. Usługa jest skojarzona z kluczem przez wywołanie AddKeyedSingleton (lub AddKeyedScoped
lub AddKeyedTransient
) w celu jej zarejestrowania. Uzyskaj dostęp do zarejestrowanej usługi, określając klucz za pomocą atrybutu [FromKeyedServices]
. Poniższy kod pokazuje, jak używać usług z przypisanymi kluczami:
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"));
}
}
Zachowanie iniekcji konstruktora
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Konteksty programu Entity Framework
Domyślnie konteksty programu Entity Framework są dodawane do kontenera usługi przy użyciu okresu istnienia o określonym zakresie , ponieważ operacje bazy danych aplikacji internetowej są zwykle ograniczone do żądania klienta. Aby użyć innej żywotności, określ ją przy użyciu przeciążenia AddDbContext. Usługi danego okresu istnienia nie powinny używać kontekstu bazy danych z okresem istnienia krótszym niż okres istnienia usługi.
Opcje okresu istnienia i rejestracji
Aby zademonstrować różnicę między okresami istnienia usługi a ich opcjami rejestracji, należy wziąć pod uwagę następujące interfejsy reprezentujące zadanie jako operację z identyfikatorem OperationId
. W zależności od tego, jak cykl życia usługi operacyjnej jest skonfigurowany dla następujących interfejsów, kontener udostępnia te same lub różne instancje usługi na żądanie klasy.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Poniższa Operation
klasa implementuje wszystkie poprzednie interfejsy. Konstruktor Operation
generuje identyfikator GUID i przechowuje ostatnie 4 znaki we OperationId
właściwości :
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Poniższy kod tworzy wiele rejestracji klasy Operation
zgodnie z nazwanymi okresami istnienia.
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();
Przykładowa aplikacja demonstruje okresy istnienia obiektów zarówno wewnątrz, jak i między żądaniami.
IndexModel
i oprogramowanie pośredniczące żądają każdego rodzaju typu IOperation
i rejestrują OperationId
dla każdego:
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);
}
}
Podobnie jak w przypadku IndexModel
, oprogramowanie pośredniczące rozwiązuje te same usługi.
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>();
}
}
Usługi o zasięgu lokalnym i tymczasowym muszą zostać rozwiązane w metodzie InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Dane wyjściowe rejestratora pokazują:
-
Obiekty przejściowe są zawsze różne. Wartość przejściowa
OperationId
różni się wIndexModel
i w oprogramowaniu pośredniczącym. - Obiekty o określonym zakresie są takie same dla danego żądania, ale różnią się w zależności od każdego nowego żądania.
- Pojedyncze obiekty są takie same dla każdego żądania.
Aby zmniejszyć dane wyjściowe rejestrowania, ustaw wartość "Logging:LogLevel:Microsoft:Error" w pliku appsettings.Development.json
.
{
"MyKey": "MyKey from appsettings.Development.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Rozwiązywanie problemów z usługą podczas uruchamiania aplikacji
Poniższy kod pokazuje, jak rozwiązać problem z usługą o określonym zakresie przez ograniczony czas trwania podczas uruchamiania aplikacji:
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();
Walidacja zakresu
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Aby uzyskać więcej informacji, zobacz Walidacja zakresu.
Zamów usługi
Usługi i ich zależności w ramach żądania ASP.NET Core są udostępniane za pośrednictwem usługi HttpContext.RequestServices.
Platforma tworzy zakres na żądanie i RequestServices
uwidacznia dostawcę usług o określonym zakresie. Wszystkie usługi o określonym zakresie są prawidłowe tak długo, jak żądanie jest aktywne.
Uwaga / Notatka
Zaleca się żądanie zależności jako parametrów konstruktora zamiast rozwiązywać usługi z RequestServices
. Żądanie zależności jako parametrów konstruktora daje klasy, które są łatwiejsze do przetestowania.
Usługi projektowe do wstrzykiwania zależności
Podczas projektowania usług do wstrzykiwania zależności:
- Unikaj stanowych, statycznych klas i składowych. Unikaj tworzenia stanu globalnego, projektując aplikacje, które korzystają z usług singletonów.
- Unikaj, bezpośredniego tworzenia wystąpień klas zależnych w ramach usług. Bezpośrednie tworzenie instancji wiąże kod z konkretną implementacją.
- Twórz usługi małe, dobrze zaprojektowane i łatwe do testowania.
Jeśli klasa ma wiele wstrzykiwanych zależności, może to wskazywać, że narusza zasadę pojedynczej odpowiedzialności (SRP) i ma zbyt wiele obowiązków. Spróbuj refaktoryzować klasę, przenosząc część swoich obowiązków do nowych klas. Należy pamiętać, że Razor klasy modeli stron i klasy kontrolerów MVC powinny skupić się na kwestiach związanych z interfejsem użytkownika.
Likwidacja usług
Kontener wywołuje Dispose dla typów IDisposable, które tworzy. Usługi pobrane z kontenera nigdy nie powinny być likwidowane przez dewelopera. Jeśli typ lub fabryka jest zarejestrowany jako singleton, kontener automatycznie usuwa singleton.
W poniższym przykładzie usługi są tworzone przez kontener usług i automatycznie usuwane: 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");
}
}
Konsola debugowania wyświetla następujące dane wyjściowe po każdym odświeżeniu strony Indeks:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Development.json
Service1.Dispose
Usługi nieutworowane przez kontener usługi
Rozważ następujący kod:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
W poprzednim kodzie:
- Wystąpienia usługi nie są tworzone przez kontener usługi.
- Platforma nie usuwa automatycznie usług.
- Deweloper jest odpowiedzialny za likwidowanie usług.
Wskazówki dotyczące interfejsu IDisposable dla wystąpień przejściowych i udostępnionych
Zobacz wskazówki dotyczące funkcji IDisposable dla przejściowej i współdzielonej instancji w wstrzykiwaniu zależności na platformie .NET.
Zastąpienie domyślnego kontenera usług
Zobacz Domyślne zastępowanie kontenerów usług w iniekcji zależności w .NET
Rekomendacje
Zobacz Zalecenia dotyczące wstrzykiwania zależności na platformie .NET
Unikaj używania wzorca lokalizatora usług. Na przykład nie należy wywoływać GetService w celu uzyskania instancji usługi, gdy można użyć DI:
niepoprawne:
Odpowiedź prawidłowa:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Unikaj innej odmiany lokalizatora usług polegającej na wstrzykiwaniu fabryki, która rozwiązuje zależności w czasie wykonywania. Obie te praktyki mieszają strategie Odwrócenia Kontroli.
Unikaj dostępu statycznego do
HttpContext
(na przykład IHttpContextAccessor.HttpContext).
DI to alternatywa dla wzorców dostępu do obiektów statycznych/globalnych. Jeśli połączysz go z dostępem do obiektów statycznych, możesz nie być w stanie zrealizować korzyści z di.
Zalecane schematy dla wielościeżnikowości w DI
Orchard Core to struktura aplikacji do tworzenia modułowych, wielodostępnych aplikacji na platformie ASP.NET Core. Aby uzyskać więcej informacji, zobacz dokumentację Orchard Core.
Zobacz przykłady Orchard Core, aby dowiedzieć się, jak tworzyć modułowe i wielodostępowe aplikacje, korzystając z Orchard Core Framework bez funkcji specyficznych dla CMS.
Usługi dostarczane przez platformę
Program.cs
rejestruje usługi używane przez aplikację, w tym funkcje platformy, takie jak Entity Framework Core i ASP.NET Core MVC. Początkowo IServiceCollection
, dostarczany do Program.cs
, zawiera usługi zdefiniowane przez framework, w zależności od tego, w jaki sposób host został skonfigurowany. W przypadku aplikacji opartych na szablonach ASP.NET Core platforma rejestruje ponad 250 usług.
W poniższej tabeli wymieniono niewielki zbiór tych usług zarejestrowanych w ramach systemu:
Typ usługi | Okres istnienia |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Przemijający |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Przemijający |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Przemijający |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Przemijający |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Dodatkowe zasoby
- Wstrzykiwanie zależności do widoków w programie ASP.NET Core
- Wstrzykiwanie zależności do kontrolerów w ASP.NET Core
- Wstrzykiwanie zależności w obsługiwaczach wymagań w ASP.NET Core
- iniekcja zależności ASP.NET Core Blazor
- Wzorce konferencji NDC na potrzeby tworzenia aplikacji di
- Uruchamianie aplikacji na platformie ASP.NET Core
- Aktywacja oprogramowania pośredniczącego opartego na fabryce na platformie ASP.NET Core
- Omówienie podstaw iniekcji zależności na platformie .NET
- Wskazówki dotyczące wstrzykiwania zależności
- Samouczek: używanie wstrzykiwania zależności na platformie .NET
- Wstrzykiwanie zależności platformy .NET
- ASP.NET Core WSTRZYKIWANIE ZALEŻNOŚCI: CO TO JEST ISERVICECOLLECTION?
- Cztery sposoby usuwania obiektów IDisposable w środowisku ASP.NET Core
- Pisanie czystego kodu w ASP.NET Core za pomocą wstrzykiwania zależności (MSDN)
- Jawna zasada zależności
- Kontenery inwersji sterowania i wzorzec wstrzykiwania zależności (Martin Fowler)
- Jak zarejestrować usługę za pomocą wielu interfejsów w usłudze ASP.NET Core DI
Przez Kirk Larkin, Steve Smith i Brandon Dahler
ASP.NET Core obsługuje wzorzec projektowania oprogramowania iniekcji zależności (DI), który jest techniką osiągnięcia inwersji kontroli (IoC) między klasami i ich zależnościami.
Aby uzyskać więcej informacji specyficznych dla wstrzykiwania zależności w kontrolerach MVC, zobacz Wstrzykiwanie zależności do kontrolerów w ASP.NET Core.
Aby uzyskać informacje na temat używania wstrzykiwania zależności w aplikacjach innych niż aplikacje internetowe, zobacz Wstrzykiwanie zależności na platformie .NET.
Aby uzyskać więcej informacji na temat wstrzykiwania zależności opcji, zobacz Wzorzec opcji w ASP.NET Core.
Ten temat zawiera informacje na temat wstrzykiwania zależności w programie ASP.NET Core. Podstawowa dokumentacja dotycząca używania wstrzykiwania zależności znajduje się w Wstrzykiwanie zależności w .NET.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Omówienie wstrzykiwania zależności
Zależność to obiekt, od którego zależy inny obiekt. Zbadaj następującą klasę MyDependency
zawierającą metodę WriteMessage
, od której zależą inne klasy.
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Klasa może utworzyć wystąpienie MyDependency
klasy w celu użycia jej WriteMessage
metody. W poniższym przykładzie MyDependency
klasa jest zależnością IndexModel
klasy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
Klasa tworzy i bezpośrednio zależy od klasy MyDependency
. Zależności kodu, takie jak w poprzednim przykładzie, są problematyczne i należy unikać z następujących powodów:
- Aby zastąpić
MyDependency
inną implementacją, należy zmodyfikować klasęIndexModel
. - Jeśli
MyDependency
ma zależności, muszą być również skonfigurowane przez klasęIndexModel
. W dużym projekcie z wieloma klasami zależnymi odMyDependency
kod konfiguracji staje się rozpraszany po całej aplikacji. - Ta implementacja jest trudna do testowania jednostkowego.
Wstrzykiwanie zależności rozwiązuje te problemy przez:
- Użycie interfejsu lub klasy bazowej do abstrakcji implementacji zależności.
- Rejestracja zależności w kontenerze usługi. ASP.NET Core udostępnia wbudowany kontener usługi. IServiceProvider Usługi są zwykle rejestrowane w pliku aplikacji
Program.cs
. - Iniekcja usługi do konstruktora klasy, w której jest używana. Struktura bierze na siebie odpowiedzialność za utworzenie wystąpienia zależności i usunięcie go, gdy nie jest już potrzebne.
W przykładowej aplikacjiIMyDependency
interfejs definiuje metodęWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Ten interfejs jest implementowany przez konkretny typ: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Przykładowa aplikacja rejestruje usługę IMyDependency
przy użyciu konkretnego typu MyDependency
. Metoda AddScoped rejestruje usługę z okresem istnienia o określonym zakresie, okres istnienia pojedynczego żądania.
Cykle życia usługi opisano w dalszej części tego tematu.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
W aplikacji przykładowej usługa IMyDependency
jest wykorzystywana do żądania i wywoływania metody WriteMessage
.
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Korzystając ze wzorca DI, kontroler lub Razor strona:
- Nie używa konkretnego typu
MyDependency
, tylko interfejsuIMyDependency
, który implementuje. Ułatwia to zmianę implementacji bez modyfikowania kontrolera lub Razor strony. - Nie tworzy wystąpienia klasy
MyDependency
, które jest tworzone przez kontener DI.
Implementację interfejsu IMyDependency
można ulepszyć przy użyciu wbudowanego interfejsu API rejestrowania:
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}");
}
}
Zaktualizowana Program.cs
wersja rejestruje nową IMyDependency
implementację:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
MyDependency2
zależy od ILogger<TCategoryName>, który jest żądany w konstruktorze.
ILogger<TCategoryName>
jest usługą zapewnianą przez platformę.
Nie jest niczym niezwykłym używanie wstrzykiwania zależności w sposób łańcuchowy. Każda żądana zależność z kolei żąda własnych zależności. Kontener rozpoznaje zależności na grafie i zwraca w pełni rozwiązaną usługę. Zbiorczy zestaw zależności, które należy rozpoznać, jest zwykle określany jako drzewo zależności, graf zależności lub graf obiektu.
Kontener rozwiązuje problem ILogger<TCategoryName>
dzięki wykorzystaniu (ogólnych) typów otwartych, eliminując konieczność rejestrowania każdego typu skonstruowanego (ogólnego).
W terminologii iniekcji zależności usługa:
- Jest to zazwyczaj obiekt, który zapewnia usługę innym obiektom, takim jak
IMyDependency
usługa. - Nie jest powiązana z usługą internetową, chociaż usługa może używać usługi internetowej.
Platforma zapewnia niezawodny system rejestrowania . Implementacje IMyDependency
pokazane w poprzednich przykładach zostały napisane, aby zademonstrować podstawowe di, a nie zaimplementować rejestrowania. Większość aplikacji nie powinna tworzyć rejestratorów. Poniższy kod demonstruje użycie domyślnego rejestrowania, które nie wymaga zarejestrowania żadnych usług:
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);
}
}
Korzystając z powyższego kodu, nie ma potrzeby aktualizowania Program.cs
elementu , ponieważ rejestrowanie jest udostępniane przez platformę.
Rejestrowanie grup usług za pomocą metod rozszerzeń
Platforma ASP.NET Core używa konwencji do rejestrowania grupy powiązanych usług. Konwencja polega na użyciu jednej Add{GROUP_NAME}
metody rozszerzenia do rejestrowania wszystkich usług wymaganych przez funkcję platformy. Na przykład AddControllers metoda rozszerzenia rejestruje usługi wymagane dla kontrolerów MVC.
Poniższy kod jest generowany przez szablon Pages Razor wykorzystując indywidualne konta użytkowników i pokazuje, jak dodać dodatkowe usługi do kontenera przy użyciu metod rozszerzeń AddDbContext i 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();
Rozważ następujące kwestie, które rejestrują usługi i konfigurują opcje:
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();
Powiązane grupy rejestracji można przenieść do metody rozszerzenia w celu zarejestrowania usług. Na przykład usługi konfiguracji są dodawane do następującej klasy:
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;
}
}
}
Pozostałe usługi są rejestrowane w podobnej klasie. Poniższy kod używa nowych metod rozszerzenia do rejestrowania usług:
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
Uwaga: Każda services.Add{GROUP_NAME}
metoda rozszerzenia dodaje i potencjalnie konfiguruje usługi. Na przykład AddControllersWithViews dodaje do usług kontrolery MVC, które wymagają widoków, a AddRazorPages dodaje usługi wymagane przez Razor Pages.
Okresy istnienia usługi
Zobacz Czas życia usług w iniekcji zależności na platformie .NET
Aby użyć usług o określonym zakresie w programie pośredniczącym, użyj jednej z następujących metod:
- Wstrzyknij usługę do metody
Invoke
lubInvokeAsync
oprogramowania pośredniczącego. Użycie iniekcji konstruktora zgłasza wyjątek w czasie wykonywania, ponieważ wymusza zachowanie usługi o określonym zakresie jak singleton. Przykład w sekcji Opcje okresu istnienia i rejestracji demonstruje podejścieInvokeAsync
. - Użyj oprogramowania pośredniczącego opartego na fabryce. Oprogramowanie pośredniczące zarejestrowane przy użyciu tego podejścia jest aktywowane na żądanie klienta (połączenie), które umożliwia wstrzyknięcie usług o określonym zakresie do konstruktora oprogramowania pośredniczącego.
Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowego oprogramowania pośredniczącego ASP.NET Core.
Metody rejestracji usługi
Zobacz Metody rejestracji usług w wstrzykiwaniu zależności na platformie .NET
Często używa się wielu implementacji podczas mockowania typów do testowania.
Zarejestrowanie usługi tylko z typem implementacji jest równoważne zarejestrowaniu tej usługi z tą samą implementacją i typem usługi. Dlatego nie można zarejestrować wielu implementacji usługi przy użyciu metod, które nie przyjmują jawnego typu usługi. Te metody mogą rejestrować wiele wystąpień usługi, ale wszystkie te metody będą miały ten sam typ implementacji .
Każda z powyższych metod rejestracji usług może służyć do rejestrowania wielu wystąpień usługi tego samego typu usługi. W poniższym przykładzie AddSingleton
jest wywoływana dwukrotnie z IMyDependency
typem usługi. Drugie wywołanie AddSingleton
przesłania poprzednie, gdy jest rozwiązane jako IMyDependency
i dodaje do poprzedniego, gdy wiele usług jest rozwiązanych poprzez IEnumerable<IMyDependency>
. Usługi są wyświetlane w kolejności, w której zostały zarejestrowane, gdy są rozwiązywane za pośrednictwem 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);
}
}
Zachowanie iniekcji konstruktora
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Konteksty programu Entity Framework
Domyślnie konteksty programu Entity Framework są dodawane do kontenera usługi przy użyciu okresu istnienia o określonym zakresie , ponieważ operacje bazy danych aplikacji internetowej są zwykle ograniczone do żądania klienta. Aby użyć innej żywotności, określ ją przy użyciu przeciążenia AddDbContext. Usługi danego okresu istnienia nie powinny używać kontekstu bazy danych z okresem istnienia krótszym niż okres istnienia usługi.
Opcje okresu istnienia i rejestracji
Aby zademonstrować różnicę między okresami istnienia usługi a ich opcjami rejestracji, należy wziąć pod uwagę następujące interfejsy reprezentujące zadanie jako operację z identyfikatorem OperationId
. W zależności od tego, jak cykl życia usługi operacyjnej jest skonfigurowany dla następujących interfejsów, kontener udostępnia te same lub różne instancje usługi na żądanie klasy.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Poniższa Operation
klasa implementuje wszystkie poprzednie interfejsy. Konstruktor Operation
generuje identyfikator GUID i przechowuje ostatnie 4 znaki we OperationId
właściwości :
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Poniższy kod tworzy wiele rejestracji klasy Operation
zgodnie z nazwanymi okresami istnienia.
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();
Przykładowa aplikacja demonstruje okresy istnienia obiektów zarówno wewnątrz, jak i między żądaniami.
IndexModel
i oprogramowanie pośredniczące żądają każdego rodzaju typu IOperation
i rejestrują OperationId
dla każdego:
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);
}
}
Podobnie jak w przypadku IndexModel
, oprogramowanie pośredniczące rozwiązuje te same usługi.
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>();
}
}
Usługi o zasięgu lokalnym i tymczasowym muszą zostać rozwiązane w metodzie InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Dane wyjściowe rejestratora pokazują:
-
Obiekty przejściowe są zawsze różne. Wartość przejściowa
OperationId
różni się wIndexModel
i w oprogramowaniu pośredniczącym. - Obiekty o określonym zakresie są takie same dla danego żądania, ale różnią się w zależności od każdego nowego żądania.
- Pojedyncze obiekty są takie same dla każdego żądania.
Aby zmniejszyć dane wyjściowe rejestrowania, ustaw wartość "Logging:LogLevel:Microsoft:Error" w pliku appsettings.Development.json
.
{
"MyKey": "MyKey from appsettings.Development.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Rozwiązywanie problemów z usługą podczas uruchamiania aplikacji
Poniższy kod pokazuje, jak rozwiązać problem z usługą o określonym zakresie przez ograniczony czas trwania podczas uruchamiania aplikacji:
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();
Walidacja zakresu
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Aby uzyskać więcej informacji, zobacz Walidacja zakresu.
Zamów usługi
Usługi i ich zależności w ramach żądania ASP.NET Core są udostępniane za pośrednictwem usługi HttpContext.RequestServices.
Platforma tworzy zakres na żądanie i RequestServices
uwidacznia dostawcę usług o określonym zakresie. Wszystkie usługi o określonym zakresie są prawidłowe tak długo, jak żądanie jest aktywne.
Uwaga / Notatka
Zaleca się żądanie zależności jako parametrów konstruktora zamiast rozwiązywać usługi z RequestServices
. Żądanie zależności jako parametrów konstruktora daje klasy, które są łatwiejsze do przetestowania.
Usługi projektowe do wstrzykiwania zależności
Podczas projektowania usług do wstrzykiwania zależności:
- Unikaj stanowych, statycznych klas i składowych. Unikaj tworzenia stanu globalnego, projektując aplikacje, które korzystają z usług singletonów.
- Unikaj, bezpośredniego tworzenia wystąpień klas zależnych w ramach usług. Bezpośrednie tworzenie instancji wiąże kod z konkretną implementacją.
- Twórz usługi małe, dobrze zaprojektowane i łatwe do testowania.
Jeśli klasa ma wiele wstrzykiwanych zależności, może to wskazywać, że narusza zasadę pojedynczej odpowiedzialności (SRP) i ma zbyt wiele obowiązków. Spróbuj refaktoryzować klasę, przenosząc część swoich obowiązków do nowych klas. Należy pamiętać, że Razor klasy modeli stron i klasy kontrolerów MVC powinny skupić się na kwestiach związanych z interfejsem użytkownika.
Likwidacja usług
Kontener wywołuje Dispose dla typów IDisposable, które tworzy. Usługi pobrane z kontenera nigdy nie powinny być likwidowane przez dewelopera. Jeśli typ lub fabryka jest zarejestrowany jako singleton, kontener automatycznie usuwa singleton.
W poniższym przykładzie usługi są tworzone przez kontener usług i automatycznie usuwane: 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");
}
}
Konsola debugowania wyświetla następujące dane wyjściowe po każdym odświeżeniu strony Indeks:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Development.json
Service1.Dispose
Usługi nieutworowane przez kontener usługi
Rozważ następujący kod:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
W poprzednim kodzie:
- Wystąpienia usługi nie są tworzone przez kontener usługi.
- Platforma nie usuwa automatycznie usług.
- Deweloper jest odpowiedzialny za likwidowanie usług.
Wskazówki dotyczące interfejsu IDisposable dla wystąpień przejściowych i udostępnionych
Zobacz wskazówki dotyczące funkcji IDisposable dla przejściowej i współdzielonej instancji w wstrzykiwaniu zależności na platformie .NET.
Zastąpienie domyślnego kontenera usług
Zobacz Domyślne zastępowanie kontenerów usług w iniekcji zależności w .NET
Rekomendacje
Zobacz Zalecenia dotyczące wstrzykiwania zależności na platformie .NET
Unikaj używania wzorca lokalizatora usług. Na przykład nie należy wywoływać GetService w celu uzyskania instancji usługi, gdy można użyć DI:
niepoprawne:
Odpowiedź prawidłowa:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Unikaj innej odmiany lokalizatora usług polegającej na wstrzykiwaniu fabryki, która rozwiązuje zależności w czasie wykonywania. Obie te praktyki mieszają strategie Odwrócenia Kontroli.
Unikaj dostępu statycznego do
HttpContext
(na przykład IHttpContextAccessor.HttpContext).
DI to alternatywa dla wzorców dostępu do obiektów statycznych/globalnych. Jeśli połączysz go z dostępem do obiektów statycznych, możesz nie być w stanie zrealizować korzyści z di.
Zalecane schematy dla wielościeżnikowości w DI
Orchard Core to struktura aplikacji do tworzenia modułowych, wielodostępnych aplikacji na platformie ASP.NET Core. Aby uzyskać więcej informacji, zobacz dokumentację Orchard Core.
Zobacz przykłady Orchard Core, aby dowiedzieć się, jak tworzyć modułowe i wielodostępowe aplikacje, korzystając z Orchard Core Framework bez funkcji specyficznych dla CMS.
Usługi dostarczane przez platformę
Program.cs
rejestruje usługi używane przez aplikację, w tym funkcje platformy, takie jak Entity Framework Core i ASP.NET Core MVC. Początkowo IServiceCollection
, dostarczany do Program.cs
, zawiera usługi zdefiniowane przez framework, w zależności od tego, w jaki sposób host został skonfigurowany. W przypadku aplikacji opartych na szablonach ASP.NET Core platforma rejestruje ponad 250 usług.
W poniższej tabeli wymieniono niewielki zbiór tych usług zarejestrowanych w ramach systemu:
Typ usługi | Okres istnienia |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Przemijający |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Przemijający |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Przemijający |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Przemijający |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Dodatkowe zasoby
- Wstrzykiwanie zależności do widoków w programie ASP.NET Core
- Wstrzykiwanie zależności do kontrolerów w ASP.NET Core
- Wstrzykiwanie zależności w obsługiwaczach wymagań w ASP.NET Core
- iniekcja zależności ASP.NET Core Blazor
- Wzorce konferencji NDC na potrzeby tworzenia aplikacji di
- Uruchamianie aplikacji na platformie ASP.NET Core
- Aktywacja oprogramowania pośredniczącego opartego na fabryce na platformie ASP.NET Core
- Cztery sposoby usuwania obiektów IDisposable w środowisku ASP.NET Core
- Pisanie czystego kodu w ASP.NET Core za pomocą wstrzykiwania zależności (MSDN)
- Jawna zasada zależności
- Kontenery inwersji sterowania i wzorzec wstrzykiwania zależności (Martin Fowler)
- Jak zarejestrować usługę za pomocą wielu interfejsów w usłudze ASP.NET Core DI
Autor: Kirk Larkin, Steve Smith, Scott Addie i Brandon Dahler
ASP.NET Core obsługuje wzorzec projektowania oprogramowania iniekcji zależności (DI), który jest techniką osiągnięcia inwersji kontroli (IoC) między klasami i ich zależnościami.
Aby uzyskać więcej informacji specyficznych dla wstrzykiwania zależności w kontrolerach MVC, zobacz Wstrzykiwanie zależności do kontrolerów w ASP.NET Core.
Aby uzyskać informacje na temat używania wstrzykiwania zależności w aplikacjach innych niż aplikacje internetowe, zobacz Wstrzykiwanie zależności na platformie .NET.
Aby uzyskać więcej informacji na temat wstrzykiwania zależności opcji, zobacz Wzorzec opcji w ASP.NET Core.
Ten temat zawiera informacje na temat wstrzykiwania zależności w programie ASP.NET Core. Podstawowa dokumentacja dotycząca używania wstrzykiwania zależności znajduje się w Wstrzykiwanie zależności w .NET.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Omówienie wstrzykiwania zależności
Zależność to obiekt, od którego zależy inny obiekt. Zbadaj następującą klasę MyDependency
zawierającą metodę WriteMessage
, od której zależą inne klasy.
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Klasa może utworzyć wystąpienie MyDependency
klasy w celu użycia jej WriteMessage
metody. W poniższym przykładzie MyDependency
klasa jest zależnością IndexModel
klasy:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet created this message.");
}
}
Klasa tworzy i bezpośrednio zależy od klasy MyDependency
. Zależności kodu, takie jak w poprzednim przykładzie, są problematyczne i należy unikać z następujących powodów:
- Aby zastąpić
MyDependency
inną implementacją, należy zmodyfikować klasęIndexModel
. - Jeśli
MyDependency
ma zależności, muszą być również skonfigurowane przez klasęIndexModel
. W dużym projekcie z wieloma klasami zależnymi odMyDependency
kod konfiguracji staje się rozpraszany po całej aplikacji. - Ta implementacja jest trudna do testowania jednostkowego. Aplikacja powinna używać makiety lub klasy wycinkowej
MyDependency
, która nie jest możliwa w przypadku tego podejścia.
Wstrzykiwanie zależności rozwiązuje te problemy przez:
- Użycie interfejsu lub klasy bazowej do abstrakcji implementacji zależności.
- Rejestracja zależności w kontenerze usługi. ASP.NET Core udostępnia wbudowany kontener usługi. IServiceProvider Usługi są zwykle rejestrowane w metodzie aplikacji
Startup.ConfigureServices
. - Iniekcja usługi do konstruktora klasy, w której jest używana. Struktura bierze na siebie odpowiedzialność za utworzenie wystąpienia zależności i usunięcie go, gdy nie jest już potrzebne.
W przykładowej aplikacjiIMyDependency
interfejs definiuje metodęWriteMessage
:
public interface IMyDependency
{
void WriteMessage(string message);
}
Ten interfejs jest implementowany przez konkretny typ: MyDependency
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
Przykładowa aplikacja rejestruje usługę IMyDependency
przy użyciu konkretnego typu MyDependency
. Metoda AddScoped rejestruje usługę z okresem istnienia o określonym zakresie, okres istnienia pojedynczego żądania.
Cykle życia usługi opisano w dalszej części tego tematu.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddRazorPages();
}
W aplikacji przykładowej usługa IMyDependency
jest wykorzystywana do żądania i wywoływania metody WriteMessage
.
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Stosując wzorzec DI, kontroler:
- Nie używa konkretnego typu
MyDependency
, tylko interfejsuIMyDependency
, który implementuje. Ułatwia to zmianę implementacji używanej przez kontroler bez modyfikowania kontrolera. - Nie tworzy wystąpienia klasy
MyDependency
, które jest tworzone przez kontener DI.
Implementację interfejsu IMyDependency
można ulepszyć przy użyciu wbudowanego interfejsu API rejestrowania:
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}");
}
}
Zaktualizowana ConfigureServices
metoda rejestruje nową IMyDependency
implementację:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency2>();
services.AddRazorPages();
}
MyDependency2
zależy od ILogger<TCategoryName>, który jest żądany w konstruktorze.
ILogger<TCategoryName>
jest usługą zapewnianą przez platformę.
Nie jest niczym niezwykłym używanie wstrzykiwania zależności w sposób łańcuchowy. Każda żądana zależność z kolei żąda własnych zależności. Kontener rozpoznaje zależności na grafie i zwraca w pełni rozwiązaną usługę. Zbiorczy zestaw zależności, które należy rozpoznać, jest zwykle określany jako drzewo zależności, graf zależności lub graf obiektu.
Kontener rozwiązuje problem ILogger<TCategoryName>
dzięki wykorzystaniu (ogólnych) typów otwartych, eliminując konieczność rejestrowania każdego typu skonstruowanego (ogólnego).
W terminologii iniekcji zależności usługa:
- Jest to zazwyczaj obiekt, który zapewnia usługę innym obiektom, takim jak
IMyDependency
usługa. - Nie jest powiązana z usługą internetową, chociaż usługa może używać usługi internetowej.
Platforma zapewnia niezawodny system rejestrowania . Implementacje IMyDependency
pokazane w poprzednich przykładach zostały napisane, aby zademonstrować podstawowe di, a nie zaimplementować rejestrowania. Większość aplikacji nie powinna tworzyć rejestratorów. Poniższy kod demonstruje użycie domyślnego rejestrowania, które nie wymaga zarejestrowania żadnych usług w programie 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);
}
}
Korzystając z powyższego kodu, nie ma potrzeby aktualizowania ConfigureServices
elementu , ponieważ rejestrowanie jest udostępniane przez platformę.
Usługi wstrzykiwane do procesu uruchamiania
Usługi można wstrzykiwać do konstruktora Startup
i metody Startup.Configure
.
Tylko następujące usługi można wstrzyknąć do konstruktora Startup
podczas korzystania z hosta ogólnego (IHostBuilder):
Do metody można wprowadzić dowolną usługę zarejestrowaną w kontenerze Startup.Configure
DI:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
...
}
Aby uzyskać więcej informacji, zobacz Uruchamianie aplikacji w ASP.NET Core i Dostęp do konfiguracji w uruchamianiu.
Rejestrowanie grup usług za pomocą metod rozszerzeń
Platforma ASP.NET Core używa konwencji do rejestrowania grupy powiązanych usług. Konwencja polega na użyciu jednej Add{GROUP_NAME}
metody rozszerzenia do rejestrowania wszystkich usług wymaganych przez funkcję platformy. Na przykład AddControllers metoda rozszerzenia rejestruje usługi wymagane dla kontrolerów MVC.
Poniższy kod jest generowany przez szablon Pages Razor wykorzystując indywidualne konta użytkowników i pokazuje, jak dodać dodatkowe usługi do kontenera przy użyciu metod rozszerzeń AddDbContext i 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();
}
Rozważmy następującą ConfigureServices
metodę, która rejestruje usługi i konfiguruje opcje:
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();
}
Powiązane grupy rejestracji można przenieść do metody rozszerzenia w celu zarejestrowania usług. Na przykład usługi konfiguracji są dodawane do następującej klasy:
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;
}
}
}
Pozostałe usługi są rejestrowane w podobnej klasie.
ConfigureServices
Poniższa metoda używa nowych metod rozszerzenia do rejestrowania usług:
public void ConfigureServices(IServiceCollection services)
{
services.AddConfig(Configuration)
.AddMyDependencyGroup();
services.AddRazorPages();
}
Uwaga: Każda services.Add{GROUP_NAME}
metoda rozszerzenia dodaje i potencjalnie konfiguruje usługi. Na przykład AddControllersWithViews dodaje do usług kontrolery MVC, które wymagają widoków, a AddRazorPages dodaje usługi wymagane przez Razor Pages. Zalecamy, aby aplikacje przestrzegały konwencji nazewnictwa tworzenia metod rozszerzeń w Microsoft.Extensions.DependencyInjection przestrzeni nazw. Tworzenie metod rozszerzeń w Microsoft.Extensions.DependencyInjection
przestrzeni nazw:
- Hermetyzuje grupy rejestracji usług.
- Zapewnia wygodny dostęp do usługi IntelliSense.
Okresy istnienia usługi
Zobacz Czas życia usług w iniekcji zależności na platformie .NET
Aby użyć usług o określonym zakresie w programie pośredniczącym, użyj jednej z następujących metod:
- Wstrzyknij usługę do metody
Invoke
lubInvokeAsync
oprogramowania pośredniczącego. Użycie iniekcji konstruktora zgłasza wyjątek w czasie wykonywania, ponieważ wymusza zachowanie usługi o określonym zakresie jak singleton. Przykład w sekcji Opcje okresu istnienia i rejestracji demonstruje podejścieInvokeAsync
. - Użyj oprogramowania pośredniczącego opartego na fabryce. Oprogramowanie pośredniczące zarejestrowane przy użyciu tego podejścia jest aktywowane na żądanie klienta (połączenie), co umożliwia wstrzyknięcie usług o określonym zakresie do metody oprogramowania pośredniczącego
InvokeAsync
.
Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowego oprogramowania pośredniczącego ASP.NET Core.
Metody rejestracji usługi
Zobacz Metody rejestracji usług w wstrzykiwaniu zależności na platformie .NET
Często używa się wielu implementacji podczas mockowania typów do testowania.
Zarejestrowanie usługi tylko z typem implementacji jest równoważne zarejestrowaniu tej usługi z tą samą implementacją i typem usługi. Dlatego nie można zarejestrować wielu implementacji usługi przy użyciu metod, które nie przyjmują jawnego typu usługi. Te metody mogą rejestrować wiele wystąpień usługi, ale wszystkie te metody będą miały ten sam typ implementacji .
Każda z powyższych metod rejestracji usług może służyć do rejestrowania wielu wystąpień usługi tego samego typu usługi. W poniższym przykładzie AddSingleton
jest wywoływana dwukrotnie z IMyDependency
typem usługi. Drugie wywołanie AddSingleton
przesłania poprzednie, gdy jest rozwiązane jako IMyDependency
i dodaje do poprzedniego, gdy wiele usług jest rozwiązanych poprzez IEnumerable<IMyDependency>
. Usługi są wyświetlane w kolejności, w której zostały zarejestrowane, gdy są rozwiązywane za pośrednictwem 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);
}
}
Zachowanie iniekcji konstruktora
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Konteksty programu Entity Framework
Domyślnie konteksty programu Entity Framework są dodawane do kontenera usługi przy użyciu okresu istnienia o określonym zakresie , ponieważ operacje bazy danych aplikacji internetowej są zwykle ograniczone do żądania klienta. Aby użyć innej żywotności, określ ją przy użyciu przeciążenia AddDbContext. Usługi danego okresu istnienia nie powinny używać kontekstu bazy danych z okresem istnienia krótszym niż okres istnienia usługi.
Opcje okresu istnienia i rejestracji
Aby zademonstrować różnicę między okresami istnienia usługi a ich opcjami rejestracji, należy wziąć pod uwagę następujące interfejsy reprezentujące zadanie jako operację z identyfikatorem OperationId
. W zależności od tego, jak cykl życia usługi operacyjnej jest skonfigurowany dla następujących interfejsów, kontener udostępnia te same lub różne instancje usługi na żądanie klasy.
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
Poniższa Operation
klasa implementuje wszystkie poprzednie interfejsy. Konstruktor Operation
generuje identyfikator GUID i przechowuje ostatnie 4 znaki we OperationId
właściwości :
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}
public string OperationId { get; }
}
Metoda Startup.ConfigureServices
tworzy wiele rejestracji klasy Operation
zgodnie z nazwami okresów istnienia:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddRazorPages();
}
Przykładowa aplikacja demonstruje okresy istnienia obiektów zarówno wewnątrz, jak i między żądaniami.
IndexModel
i oprogramowanie pośredniczące żądają każdego rodzaju typu IOperation
i rejestrują OperationId
dla każdego:
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);
}
}
Podobnie jak w przypadku IndexModel
, oprogramowanie pośredniczące rozwiązuje te same usługi.
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>();
}
}
Usługi o określonym zakresie muszą zostać rozwiązane w metodzie InvokeAsync
:
public async Task InvokeAsync(HttpContext context,
IOperationScoped scopedOperation)
{
_logger.LogInformation("Transient: " + _transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
await _next(context);
}
Dane wyjściowe rejestratora pokazują:
-
Obiekty przejściowe są zawsze różne. Wartość przejściowa
OperationId
różni się wIndexModel
i w oprogramowaniu pośredniczącym. - Obiekty o określonym zakresie są takie same dla danego żądania, ale różnią się w zależności od każdego nowego żądania.
- Pojedyncze obiekty są takie same dla każdego żądania.
Aby zmniejszyć dane wyjściowe rejestrowania, ustaw wartość "Logging:LogLevel:Microsoft:Error" w pliku appsettings.Development.json
.
{
"MyKey": "MyKey from appsettings.Development.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
Call services from main (Wywołanie usług z sieci głównej)
Utwórz element IServiceScope za pomocą IServiceScopeFactory.CreateScope , aby uzyskać usługę o określonym zakresie w obrębie aplikacji. Takie podejście jest przydatne do uzyskiwania dostępu do usługi z określonym zasięgiem przy uruchomieniu, aby wykonać zadania inicjalizacyjne.
W poniższym przykładzie pokazano, jak uzyskać dostęp do usługi o IMyDependency
określonym zakresie i wywołać jej WriteMessage
metodę w pliku 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>();
});
}
Walidacja zakresu
Zobacz Zachowanie iniekcji konstruktora w iniekcji zależności na platformie .NET
Aby uzyskać więcej informacji, zobacz Walidacja zakresu.
Zamów usługi
Usługi i ich zależności w ramach żądania ASP.NET Core są udostępniane za pośrednictwem usługi HttpContext.RequestServices.
Platforma tworzy zakres na żądanie i RequestServices
uwidacznia dostawcę usług o określonym zakresie. Wszystkie usługi o określonym zakresie są prawidłowe tak długo, jak żądanie jest aktywne.
Uwaga / Notatka
Zaleca się żądanie zależności jako parametrów konstruktora zamiast rozwiązywać usługi z RequestServices
. Żądanie zależności jako parametrów konstruktora daje klasy, które są łatwiejsze do przetestowania.
Usługi projektowe do wstrzykiwania zależności
Podczas projektowania usług do wstrzykiwania zależności:
- Unikaj stanowych, statycznych klas i składowych. Unikaj tworzenia stanu globalnego, projektując aplikacje, które korzystają z usług singletonów.
- Unikaj, bezpośredniego tworzenia wystąpień klas zależnych w ramach usług. Bezpośrednie tworzenie instancji wiąże kod z konkretną implementacją.
- Twórz usługi małe, dobrze zaprojektowane i łatwe do testowania.
Jeśli klasa ma wiele wstrzykiwanych zależności, może to wskazywać, że narusza zasadę pojedynczej odpowiedzialności (SRP) i ma zbyt wiele obowiązków. Spróbuj refaktoryzować klasę, przenosząc część swoich obowiązków do nowych klas. Należy pamiętać, że Razor klasy modeli stron i klasy kontrolerów MVC powinny skupić się na kwestiach związanych z interfejsem użytkownika.
Likwidacja usług
Kontener wywołuje Dispose dla typów IDisposable, które tworzy. Usługi pobrane z kontenera nigdy nie powinny być likwidowane przez dewelopera. Jeśli typ lub fabryka jest zarejestrowany jako singleton, kontener automatycznie usuwa singleton.
W poniższym przykładzie usługi są tworzone przez kontener usługi i usuwane automatycznie:
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");
}
}
Konsola debugowania wyświetla następujące dane wyjściowe po każdym odświeżeniu strony Indeks:
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose
Usługi nieutworowane przez kontener usługi
Rozważ następujący kod:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new Service1());
services.AddSingleton(new Service2());
services.AddRazorPages();
}
W poprzednim kodzie:
- Wystąpienia usługi nie są tworzone przez kontener usługi.
- Platforma nie usuwa automatycznie usług.
- Deweloper jest odpowiedzialny za likwidowanie usług.
Wskazówki dotyczące interfejsu IDisposable dla wystąpień przejściowych i udostępnionych
Zobacz wskazówki dotyczące funkcji IDisposable dla przejściowej i współdzielonej instancji w wstrzykiwaniu zależności na platformie .NET.
Zastąpienie domyślnego kontenera usług
Zobacz Domyślne zastępowanie kontenerów usług w iniekcji zależności w .NET
Rekomendacje
Zobacz Zalecenia dotyczące wstrzykiwania zależności na platformie .NET
Unikaj używania wzorca lokalizatora usług. Na przykład nie należy wywoływać GetService w celu uzyskania instancji usługi, gdy można użyć DI:
niepoprawne:
Odpowiedź prawidłowa:
public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
Unikaj innej odmiany lokalizatora usług polegającej na wstrzykiwaniu fabryki, która rozwiązuje zależności w czasie wykonywania. Obie te praktyki mieszają strategie Odwrócenia Kontroli.
Unikaj dostępu statycznego do
HttpContext
(na przykład IHttpContextAccessor.HttpContext).
Unikaj wywołań BuildServiceProvider w
ConfigureServices
. WywołanieBuildServiceProvider
zwykle występuje, gdy deweloper chce rozwiązać problem z usługą w programieConfigureServices
. Rozważmy na przykład przypadek, w którym elementLoginPath
jest ładowany z konfiguracji. Unikaj następującego podejścia:Na poprzedniej ilustracji wybranie zielonej linii falistej poniżej
services.BuildServiceProvider
pokazuje następujące ostrzeżenie ASP0000:Wywołanie "BuildServiceProvider" z kodu aplikacji (ASP0000) powoduje utworzenie dodatkowej kopii usług singleton. Rozważ alternatywy, takie jak wstrzykiwanie usług zależności jako parametry do "Konfiguruj".
Wywołanie
BuildServiceProvider
tworzy drugi kontener, co może prowadzić do podzielonych singletonów i spowodować odwołania do grafów obiektów między wieloma kontenerami.Prawidłowym sposobem uzyskania
LoginPath
jest użycie wbudowanej obsługi wzorca opcji dla DI:public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); services.AddOptions<CookieAuthenticationOptions>( CookieAuthenticationDefaults.AuthenticationScheme) .Configure<IMyService>((options, myService) => { options.LoginPath = myService.GetLoginPath(); }); services.AddRazorPages(); }
Jednorazowe usługi przejściowe są zarządzane przez pojemnik w celu usunięcia. Może to przekształcić się w przeciek pamięci, jeśli zostanie rozwiązany z kontenera najwyższego poziomu.
Włącz walidację zakresu, żeby upewnić się, że aplikacja nie ma singletonów, które przechwytują usługi o zmiennym zakresie. Aby uzyskać więcej informacji, zobacz Walidacja zakresu.
Podobnie jak w przypadku wszystkich zestawów zaleceń, mogą wystąpić sytuacje, w których wymagane jest ignorowanie rekomendacji. Wyjątki są rzadkie, głównie specjalne przypadki w ramach samej struktury.
DI to alternatywa dla wzorców dostępu do obiektów statycznych/globalnych. Jeśli połączysz go z dostępem do obiektów statycznych, możesz nie być w stanie zrealizować korzyści z di.
Zalecane schematy dla wielościeżnikowości w DI
Orchard Core to struktura aplikacji do tworzenia modułowych, wielodostępnych aplikacji na platformie ASP.NET Core. Aby uzyskać więcej informacji, zobacz dokumentację Orchard Core.
Zobacz przykłady Orchard Core, aby dowiedzieć się, jak tworzyć modułowe i wielodostępowe aplikacje, korzystając z Orchard Core Framework bez funkcji specyficznych dla CMS.
Usługi dostarczane przez platformę
Metoda Startup.ConfigureServices
rejestruje usługi używane przez aplikację, w tym funkcje platformy, takie jak Entity Framework Core i ASP.NET Core MVC. Początkowo IServiceCollection
, dostarczany do ConfigureServices
, zawiera usługi zdefiniowane przez framework, w zależności od tego, w jaki sposób host został skonfigurowany. W przypadku aplikacji opartych na szablonach ASP.NET Core platforma rejestruje ponad 250 usług.
W poniższej tabeli wymieniono niewielki zbiór tych usług zarejestrowanych w ramach systemu:
Typ usługi | Okres istnienia |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Przemijający |
IHostApplicationLifetime | Singleton |
IWebHostEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Przemijający |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Przemijający |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Przemijający |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
Dodatkowe zasoby
- Wstrzykiwanie zależności do widoków w programie ASP.NET Core
- Wstrzykiwanie zależności do kontrolerów w ASP.NET Core
- Wstrzykiwanie zależności w obsługiwaczach wymagań w ASP.NET Core
- iniekcja zależności ASP.NET Core Blazor
- Wzorce konferencji NDC na potrzeby tworzenia aplikacji di
- Uruchamianie aplikacji na platformie ASP.NET Core
- Aktywacja oprogramowania pośredniczącego opartego na fabryce na platformie ASP.NET Core
- Cztery sposoby usuwania obiektów IDisposable w środowisku ASP.NET Core
- Pisanie czystego kodu w ASP.NET Core za pomocą wstrzykiwania zależności (MSDN)
- Jawna zasada zależności
- Kontenery inwersji sterowania i wzorzec wstrzykiwania zależności (Martin Fowler)
- Jak zarejestrować usługę za pomocą wielu interfejsów w usłudze ASP.NET Core DI