Wstrzykiwanie zależności platformy .NET
Platforma .NET 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. Wstrzykiwanie zależności na platformie .NET jest wbudowaną częścią struktury wraz z konfiguracją, rejestrowaniem i wzorcem opcji.
Zależność to obiekt, od którego zależy inny obiekt. Zbadaj następującą MessageWriter
klasę przy użyciu metody, od których Write
zależą inne klasy:
public class MessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Klasa może utworzyć wystąpienie MessageWriter
klasy w celu użycia jej Write
metody. W poniższym przykładzie MessageWriter
klasa jest zależnością Worker
klasy:
public class Worker : BackgroundService
{
private readonly MessageWriter _messageWriter = new();
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
Klasa tworzy klasę MessageWriter
i zależy bezpośrednio od klasy. Zakodowane na twardo zależności, takie jak w poprzednim przykładzie, są problematyczne i należy unikać z następujących powodów:
- Aby zastąpić
MessageWriter
inną implementacją, należy zmodyfikować klasęWorker
. - Jeśli
MessageWriter
ma zależności, muszą być również skonfigurowane przez klasęWorker
. W dużym projekcie z wieloma klasami w zależności odMessageWriter
metody kod konfiguracji staje się rozproszony w całej aplikacji. - Ta implementacja jest trudna do testowania jednostkowego. Aplikacja powinna używać makiety lub klasy wycinkowej
MessageWriter
, która nie jest możliwa w przypadku tego podejścia.
Wstrzykiwanie zależności rozwiązuje te problemy za pomocą następujących elementów:
- Użycie interfejsu lub klasy bazowej do abstrakcji implementacji zależności.
- Rejestracja zależności w kontenerze usługi. Platforma .NET udostępnia wbudowany kontener IServiceProviderusługi . Usługi są zwykle rejestrowane podczas uruchamiania aplikacji i dołączane do elementu IServiceCollection. Po dodaniu wszystkich usług użyj BuildServiceProvider polecenia , aby utworzyć kontener usługi.
- 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.
Na przykład IMessageWriter
interfejs definiuje metodę Write
:
namespace DependencyInjection.Example;
public interface IMessageWriter
{
void Write(string message);
}
Ten interfejs jest implementowany przez konkretny typ: MessageWriter
namespace DependencyInjection.Example;
public class MessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Przykładowy kod rejestruje usługę IMessageWriter
przy użyciu konkretnego typu MessageWriter
. Metoda AddSingleton rejestruje usługę z pojedynczym okresem istnienia, okresem istnienia aplikacji. Okresy istnienia usługi zostały opisane w dalszej części tego artykułu.
using DependencyInjection.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
using IHost host = builder.Build();
host.Run();
W poprzednim kodzie przykładowa aplikacja:
Tworzy wystąpienie konstruktora aplikacji hosta.
Konfiguruje usługi, rejestrując:
- Jako
Worker
usługa hostowana. Aby uzyskać więcej informacji, zobacz Usługi robocze na platformie .NET. - Interfejs
IMessageWriter
jako pojedyncza usługa z odpowiednią implementacjąMessageWriter
klasy.
- Jako
Kompiluje hosta i uruchamia go.
Host zawiera dostawcę usługi wstrzykiwania zależności. Zawiera również wszystkie pozostałe odpowiednie usługi wymagane do automatycznego utworzenia wystąpienia elementu i dostarczenia odpowiedniej IMessageWriter
implementacji Worker
jako argumentu.
namespace DependencyInjection.Example;
public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
Korzystając ze wzorca di, usługa procesu roboczego:
- Nie używa konkretnego typu
MessageWriter
, tylkoIMessageWriter
interfejsu, który go implementuje. Ułatwia to zmianę implementacji używanej przez usługę procesu roboczego bez modyfikowania usługi procesu roboczego. - Nie tworzy wystąpienia klasy
MessageWriter
. Wystąpienie jest tworzone przez kontener DI.
Implementację interfejsu IMessageWriter
można ulepszyć przy użyciu wbudowanego interfejsu API rejestrowania:
namespace DependencyInjection.Example;
public class LoggingMessageWriter(
ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
public void Write(string message) =>
logger.LogInformation("Info: {Msg}", message);
}
Zaktualizowana AddSingleton
metoda rejestruje nową IMessageWriter
implementację:
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
Typ HostApplicationBuilder (builder
) jest częścią Microsoft.Extensions.Hosting
pakietu NuGet.
LoggingMessageWriter
ILogger<TCategoryName>zależy od elementu , który żąda w konstruktorze. ILogger<TCategoryName>
jest usługą zapewnianą przez platformę.
Nie jest niczym niezwykłym, aby używać 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).
Dzięki terminologii iniekcji zależności usługa:
- Jest to zazwyczaj obiekt, który zapewnia usługę innym obiektom, takim jak
IMessageWriter
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 IMessageWriter
pokazane w poprzednich przykładach zostały napisane, aby zademonstrować podstawowe di, a nie zaimplementować rejestrowania. Większość aplikacji nie powinna pisać rejestratorów. Poniższy kod demonstruje użycie domyślnego rejestrowania, które wymaga Worker
zarejestrowania elementu jako hostowanej usługi AddHostedService:
public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1_000, stoppingToken);
}
}
}
Korzystając z powyższego kodu, nie ma potrzeby aktualizowania Program.cs, ponieważ rejestrowanie jest udostępniane przez platformę.
Reguły odnajdywania wielu konstruktorów
Gdy typ definiuje więcej niż jeden konstruktor, dostawca usług ma logikę określania, który konstruktor ma być używany. Konstruktor z największymi parametrami, w których wybierane są typy di-rozpoznawania. Rozważmy następującą przykładową usługę w języku C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(FooService fooService, BarService barService)
{
// omitted for brevity
}
}
W poprzednim kodzie załóżmy, że rejestrowanie zostało dodane i jest możliwe do rozpoznawania od dostawcy usług, ale typy FooService
i BarService
nie są. Konstruktor z parametrem ILogger<ExampleService>
służy do rozpoznawania ExampleService
wystąpienia. Mimo że istnieje konstruktor, który definiuje więcej parametrów, FooService
typy i BarService
nie są rozpoznawane di.
Jeśli podczas odnajdywania konstruktorów występuje niejednoznaczność, zgłaszany jest wyjątek. Rozważmy następującą przykładową usługę w języku C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Ostrzeżenie
ExampleService
Kod z niejednoznacznymi parametrami typu rozpoznawania di zgłasza wyjątek. Nie należy tego robić — ma to na celu pokazanie, co jest oznaczane przez "niejednoznaczne typy rozpoznawania di".
W poprzednim przykładzie istnieją trzy konstruktory. Pierwszy konstruktor jest bez parametrów i nie wymaga żadnych usług od dostawcy usług. Załóżmy, że zarówno rejestrowanie, jak i opcje zostały dodane do kontenera DI i są usługami rozpoznawania di. Gdy kontener DI próbuje rozpoznać ExampleService
typ, zgłosi wyjątek, ponieważ dwa konstruktory są niejednoznaczne.
Można uniknąć niejednoznaczności, definiując konstruktor, który akceptuje oba typy możliwe do rozpoznawania di:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(
ILogger<ExampleService> logger,
IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Rejestrowanie grup usług za pomocą metod rozszerzeń
Rozszerzenia firmy Microsoft używają konwencji 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 AddOptions metoda rozszerzenia rejestruje wszystkie usługi wymagane do korzystania z opcji.
Usługi dostarczane przez platformę
W przypadku korzystania z dowolnego z dostępnych wzorców hosta lub konstruktora aplikacji stosowane są wartości domyślne, a usługi są rejestrowane przez platformę. Rozważ niektóre z najpopularniejszych wzorców hostów i konstruktorów aplikacji:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
Po utworzeniu konstruktora z dowolnego z tych interfejsów IServiceCollection
API program zawiera usługi zdefiniowane przez platformę, w zależności od sposobu konfiguracji hosta. W przypadku aplikacji opartych na szablonach platformy .NET platforma może zarejestrować setki usług.
W poniższej tabeli wymieniono niewielką próbkę tych usług zarejestrowanych w strukturze:
Rodzaj usługi | Okres istnienia |
---|---|
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory | Pojedyncze |
IHostApplicationLifetime | Pojedyncze |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Pojedyncze |
Microsoft.Extensions.Logging.ILoggerFactory | Pojedyncze |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Pojedyncze |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Przejściowy |
Microsoft.Extensions.Options.IOptions<TOptions> | Pojedyncze |
System.Diagnostics.DiagnosticListener | Pojedyncze |
System.Diagnostics.DiagnosticSource | Pojedyncze |
Okresy istnienia usługi
Usługi można zarejestrować w jednym z następujących okresów istnienia:
W poniższych sekcjach opisano każdy z poprzednich okresów istnienia. Wybierz odpowiedni okres istnienia dla każdej zarejestrowanej usługi.
Przejściowy
Usługi okresów przejściowych są tworzone za każdym razem, gdy są żądane z kontenera usługi. Aby zarejestrować usługę jako przejściową, wywołaj metodę AddTransient.
W aplikacjach, które przetwarzają żądania, usługi przejściowe są usuwane na końcu żądania. Ten okres istnienia wiąże się z alokacjami poszczególnych żądań, ponieważ usługi są rozwiązywane i tworzone za każdym razem. Aby uzyskać więcej informacji, zobacz Wytyczne dotyczące wstrzykiwania zależności: Wskazówki dotyczące funkcji IDisposable dla wystąpień przejściowych i udostępnionych.
Zakresu
W przypadku aplikacji internetowych okres istnienia w zakresie wskazuje, że usługi są tworzone raz na żądanie klienta (połączenie). Zarejestruj usługi o określonym zakresie za pomocą polecenia AddScoped.
W aplikacjach, które przetwarzają żądania, zakres usług jest usuwany na końcu żądania.
W przypadku korzystania z platformy Entity Framework Core AddDbContext metoda rozszerzenia domyślnie rejestruje DbContext
typy z okresem istnienia o określonym zakresie.
Uwaga
Nie należy rozwiązywać usługi o określonym zakresie z pojedynczego elementu i uważaj, aby nie robić tego pośrednio, na przykład za pośrednictwem usługi przejściowej. Może to spowodować, że usługa będzie mieć nieprawidłowy stan podczas przetwarzania kolejnych żądań. Dobrze jest:
- Rozwiąż pojedynczą usługę z usługi o określonym zakresie lub przejściowym.
- Rozwiązywanie problemu z usługą o określonym zakresie z innej usługi o określonym zakresie lub przejściowym.
Domyślnie w środowisku deweloperów rozpoznawanie usługi z innej usługi z dłuższym okresem istnienia zgłasza wyjątek. Aby uzyskać więcej informacji, zobacz Walidacja zakresu.
Pojedyncze
Pojedyncze usługi okresu istnienia są tworzone:
- Przy pierwszym żądaniu.
- Deweloper udostępnia wystąpienie implementacji bezpośrednio do kontenera. Takie podejście jest rzadko potrzebne.
Każde kolejne żądanie implementacji usługi z kontenera wstrzykiwania zależności używa tego samego wystąpienia. Jeśli aplikacja wymaga zachowania pojedynczego, zezwól kontenerowi usługi na zarządzanie okresem istnienia usługi. Nie implementuj wzorca projektu jednotonowego i nie udostępniaj kodu do usuwania pojedynczego elementu. Usługi nigdy nie powinny być usuwane przez kod, który rozpoznał usługę z kontenera. Jeśli typ lub fabryka jest rejestrowana jako pojedyncza, kontener automatycznie usuwa pojedynczyton.
Zarejestruj pojedyncze usługi za pomocą polecenia AddSingleton. Usługi singleton muszą być bezpieczne wątkami i są często używane w usługach bezstanowych.
W aplikacjach, które przetwarzają żądania, pojedyncze usługi są usuwane po usunięciu ServiceProvider po zamknięciu aplikacji. Ponieważ pamięć nie jest zwalniana, dopóki aplikacja nie zostanie zamknięta, rozważ użycie pamięci z pojedynczą usługą.
Metody rejestracji usługi
Platforma udostępnia metody rozszerzenia rejestracji usług, które są przydatne w określonych scenariuszach:
Method | Automatyczne obiekt akumulatorów |
Wiele implementacje |
Przekazywanie args |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() Przykład: services.AddSingleton<IMyDep, MyDep>(); |
Tak | Tak | Nie. |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) Przykłady: services.AddSingleton<IMyDep>(sp => new MyDep()); services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
Tak | Tak | Tak |
Add{LIFETIME}<{IMPLEMENTATION}>() Przykład: services.AddSingleton<MyDep>(); |
Tak | Nie. | Nie. |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) Przykłady: services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep(99)); |
Nie. | Tak | Tak |
AddSingleton(new {IMPLEMENTATION}) Przykłady: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); |
Nie | Nie. | Tak |
Aby uzyskać więcej informacji na temat usuwania typu, zobacz sekcję Usuwanie usług .
Zarejestrowanie usługi tylko z typem implementacji jest równoważne zarejestrowaniu tej usługi z tą samą implementacją i typem usługi. Rozważmy na przykład następujący kod:
services.AddSingleton<ExampleService>();
Jest to odpowiednik rejestrowania usługi zarówno z usługą, jak i implementacją tych samych typów:
services.AddSingleton<ExampleService, ExampleService>();
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 IMessageWriter
typem usługi. Drugie wywołanie AddSingleton
przesłonięcia poprzedniego, gdy zostanie rozpoznane jako IMessageWriter
i dodaje do poprzedniego, gdy wiele usług jest rozpoznawanych za pośrednictwem metody IEnumerable<IMessageWriter>
. Usługi są wyświetlane w kolejności, w której zostały zarejestrowane po rozwiązaniu za pośrednictwem .IEnumerable<{SERVICE}>
using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();
using IHost host = builder.Build();
_ = host.Services.GetService<ExampleService>();
await host.RunAsync();
Poprzedni przykładowy kod źródłowy rejestruje dwie implementacje obiektu IMessageWriter
.
using System.Diagnostics;
namespace ConsoleDI.IEnumerableExample;
public sealed class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is LoggingMessageWriter);
var dependencyArray = messageWriters.ToArray();
Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
}
}
Definiuje ExampleService
dwa parametry konstruktora: jeden IMessageWriter
i .IEnumerable<IMessageWriter>
IMessageWriter
Pojedyncza jest ostatnią implementacją, która została zarejestrowana, natomiast IEnumerable<IMessageWriter>
reprezentuje wszystkie zarejestrowane implementacje.
Platforma udostępnia TryAdd{LIFETIME}
również metody rozszerzeń, które rejestrują usługę tylko wtedy, gdy nie ma jeszcze zarejestrowanej implementacji.
W poniższym przykładzie wywołanie metody AddSingleton
rejestrowania ConsoleMessageWriter
się jako implementacja dla elementu IMessageWriter
. Wywołanie polecenia TryAddSingleton
nie ma wpływu, ponieważ IMessageWriter
ma już zarejestrowaną implementację:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
Element TryAddSingleton
nie ma żadnego wpływu, ponieważ został już dodany, a "spróbuj" zakończy się niepowodzeniem. Twierdzenie ExampleService
będzie następujące:
public class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is ConsoleMessageWriter);
Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
}
}
Aby uzyskać więcej informacji, zobacz:
Metody TryAddEnumerable(ServiceDescriptor) rejestrują usługę tylko wtedy, gdy nie ma jeszcze implementacji tego samego typu. Wiele usług jest rozwiązywanych za pośrednictwem metody IEnumerable<{SERVICE}>
. Podczas rejestrowania usług dodaj wystąpienie, jeśli jeden z tych samych typów nie został jeszcze dodany. Autorzy bibliotek używają TryAddEnumerable
metody , aby uniknąć rejestrowania wielu kopii implementacji w kontenerze.
W poniższym przykładzie pierwsze wywołanie rejestru TryAddEnumerable
się MessageWriter
jako implementacja dla elementu IMessageWriter1
. Drugie wywołanie rejestruje się MessageWriter
dla elementu IMessageWriter2
. Trzecie wywołanie nie ma wpływu, ponieważ IMessageWriter1
już zarejestrowano implementację MessageWriter
elementu :
public interface IMessageWriter1 { }
public interface IMessageWriter2 { }
public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
Rejestracja usługi jest ogólnie niezależna od kolejności, z wyjątkiem rejestracji wielu implementacji tego samego typu.
IServiceCollection
jest kolekcją ServiceDescriptor obiektów. W poniższym przykładzie pokazano, jak zarejestrować usługę przez utworzenie i dodanie elementu ServiceDescriptor
:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
Wbudowane Add{LIFETIME}
metody używają tego samego podejścia. Zobacz na przykład kod źródłowy AddScoped.
Zachowanie iniekcji konstruktora
Usługi można rozwiązać przy użyciu:
- IServiceProvider
- ActivatorUtilities:
- Tworzy obiekty, które nie są zarejestrowane w kontenerze.
- Używany z niektórymi funkcjami platformy.
Konstruktory mogą akceptować argumenty, które nie są dostarczane przez iniekcję zależności, ale argumenty muszą przypisywać wartości domyślne.
Gdy usługi są rozpoznawane przez IServiceProvider
metodę lub ActivatorUtilities
, wstrzykiwanie konstruktora wymaga publicznego konstruktora.
Gdy usługi są rozpoznawane przez ActivatorUtilities
metodę , wstrzykiwanie konstruktora wymaga, aby istnieje tylko jeden odpowiedni konstruktor. Przeciążenia konstruktora są obsługiwane, ale tylko jedno przeciążenie może istnieć, którego argumenty mogą być spełnione przez wstrzykiwanie zależności.
Walidacja zakresu
Gdy aplikacja działa w Development
środowisku i wywołuje metodę CreateApplicationBuilder w celu skompilowania hosta, domyślny dostawca usług przeprowadza kontrole w celu sprawdzenia, czy:
- Usługi o określonym zakresie nie są rozpoznawane przez głównego dostawcę usług.
- Usługi o określonym zakresie nie są wstrzykiwane do pojedynczychtonów.
Dostawca usługi głównej jest tworzony, gdy BuildServiceProvider jest wywoływany. Okres istnienia głównego dostawcy usług odpowiada okresowi istnienia aplikacji, gdy dostawca rozpoczyna się od aplikacji i jest usuwany po zamknięciu aplikacji.
Usługi o określonym zakresie są usuwane przez kontener, który je utworzył. Jeśli usługa o określonym zakresie jest tworzona w kontenerze głównym, okres istnienia usługi jest skutecznie promowany do pojedynczegotonu, ponieważ jest on usuwany tylko przez kontener główny po zamknięciu aplikacji. Sprawdzanie poprawności zakresów usługi przechwytuje te sytuacje, gdy BuildServiceProvider
jest wywoływana.
Scenariusze zakresu
Element IServiceScopeFactory jest zawsze rejestrowany jako pojedynczy, ale IServiceProvider może się różnić w zależności od okresu istnienia zawierającej klasy. Jeśli na przykład rozwiążesz problemy z usługami z zakresu, a każda z tych usług użyje IServiceProviderklasy , będzie to wystąpienie o określonym zakresie.
Aby osiągnąć usługi określania zakresu w ramach implementacji IHostedServiceprogramu , takich jak BackgroundService, nie należy wprowadzać zależności usługi za pomocą iniekcji konstruktora. Zamiast tego należy wstrzyknąć IServiceScopeFactory, utworzyć zakres, a następnie rozwiązać zależności z zakresu, aby użyć odpowiedniego okresu istnienia usługi.
namespace WorkerScope.Example;
public sealed class Worker(
ILogger<Worker> logger,
IServiceScopeFactory serviceScopeFactory)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
try
{
logger.LogInformation(
"Starting scoped work, provider hash: {hash}.",
scope.ServiceProvider.GetHashCode());
var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
var next = await store.GetNextAsync();
logger.LogInformation("{next}", next);
var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
await processor.ProcessAsync(next);
logger.LogInformation("Processing {name}.", next.Name);
var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
await relay.RelayAsync(next);
logger.LogInformation("Processed results have been relayed.");
var marked = await store.MarkAsync(next);
logger.LogInformation("Marked as processed: {next}", marked);
}
finally
{
logger.LogInformation(
"Finished scoped work, provider hash: {hash}.{nl}",
scope.ServiceProvider.GetHashCode(), Environment.NewLine);
}
}
}
}
}
W poprzednim kodzie, gdy aplikacja jest uruchomiona, usługa w tle:
- Zależy od .IServiceScopeFactory
- Tworzy element IServiceScope do rozpoznawania dodatkowych usług.
- Rozwiązuje zakres usług do użycia.
- Działa na temat przetwarzania obiektów, a następnie przekazywania ich, a na koniec oznacza je jako przetworzone.
W przykładowym kodzie źródłowym można zobaczyć, jak implementacje IHostedService mogą korzystać z okresów istnienia usługi w zakresie.
Usługi kluczy
Począwszy od platformy .NET 8, dostępna jest obsługa rejestracji usług i wyszukiwań na podstawie klucza, co oznacza, że istnieje możliwość zarejestrowania wielu usług przy użyciu innego klucza i użycia tego klucza do wyszukiwania.
Rozważmy na przykład przypadek, w którym istnieją różne implementacje interfejsu IMessageWriter
: MemoryMessageWriter
i QueueMessageWriter
.
Te usługi można zarejestrować przy użyciu przeciążenia metod rejestracji usługi (widocznych wcześniej), które obsługują klucz jako parametr:
services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");
Parametr key
nie jest ograniczony do string
parametru , może to być dowolny object
element, o ile typ poprawnie implementuje Equals
wartość .
W konstruktorze klasy używającej IMessageWriter
klasy należy dodać element FromKeyedServicesAttribute , aby określić klucz usługi do rozpoznania:
public class ExampleService
{
public ExampleService(
[FromKeyedServices("queue")] IMessageWriter writer)
{
// Omitted for brevity...
}
}
Zobacz też
- Omówienie podstaw iniekcji zależności na platformie .NET
- Używanie wstrzykiwania zależności na platformie .NET
- Wskazówki dotyczące wstrzykiwania zależności
- Dependency injection in ASP.NET Core (Wstrzykiwanie zależności na platformie ASP.NET Core)
- Wzorce konferencji NDC na potrzeby tworzenia aplikacji di
- Reguła jawnych zależności
- Inwersja kontenerów kontrolek i wzorzec wstrzykiwania zależności (Martin Fowler)
- Błędy di powinny zostać utworzone w repozytorium github.com/dotnet/extensions