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 od MessageWritermetody 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.
  • 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, tylko IMessageWriter 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.

LoggingMessageWriterILogger<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 class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger) =>
        _logger = logger;

    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:

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:

Typ 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. 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 IMessageWriteri .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ę MessageWriterelementu :

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:

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 ActivatorUtilitiesmetodę , 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 stringparametru , może to być dowolny object element, o ile typ poprawnie implementuje Equalswartość .

W konstruktorze klasy używającej IMessageWriterklasy 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ż