Injektáž závislostí .NET

.NET podporuje model návrhu softwaru injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi. Injektáž závislostí v .NET je integrovaná součást architektury spolu s konfigurací, protokolováním a vzorem možností.

Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MessageWriter třídu pomocí Write metody, na které závisí jiné třídy:

public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

Třída může vytvořit instanci MessageWriter třídy, aby využívala její Write metodu. V následujícím příkladu MessageWriter je třída závislostí Worker třídy:

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);
        }
    }
}

Třída vytvoří a přímo závisí na MessageWriter třídě. Pevně zakódované závislosti, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:

  • Chcete-li nahradit MessageWriter jinou implementací, Worker musí být třída změněna.
  • Pokud MessageWriter obsahuje závislosti, musí je také nakonfigurovat Worker třída. Ve velkém projektu s více třídami v závislosti na MessageWritertom se konfigurační kód rozdělí v aplikaci.
  • Tato implementace je obtížná pro testování jednotek. Aplikace by měla používat napodobení nebo třídu zástupných procedur MessageWriter , která s tímto přístupem není možná.

Injektáž závislostí řeší tyto problémy prostřednictvím:

  • Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
  • Registrace závislosti v kontejneru služby .NET poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v start-upu aplikace a připojí se k sadě IServiceCollection. Po přidání všech služeb vytvoříte BuildServiceProvider kontejner služby.
  • Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.

IMessageWriter Například rozhraní definuje metoduWrite:

namespace DependencyInjection.Example;

public interface IMessageWriter
{
    void Write(string message);
}

Toto rozhraní je implementováno konkrétním typem: MessageWriter

namespace DependencyInjection.Example;

public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

Vzorový kód zaregistruje IMessageWriter službu s konkrétním typem MessageWriter. Metoda AddSingleton zaregistruje službu s jednou životností, životností aplikace. Životnosti služeb jsou popsány dále v tomto článku.

using DependencyInjection.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();

using IHost host = builder.Build();

host.Run();

V předchozím kódu ukázková aplikace:

  • Vytvoří instanci tvůrce hostitelských aplikací.

  • Nakonfiguruje služby registrací:

    • Jako Worker hostovaná služba. Další informace naleznete v tématu Pracovní služby v .NET.
    • Rozhraní IMessageWriter jako jednoúčelová služba s odpovídající implementací MessageWriter třídy.
  • Vytvoří hostitele a spustí ho.

Hostitel obsahuje poskytovatele služby injektáže závislostí. Obsahuje také všechny ostatní relevantní služby potřebné k automatickému vytvoření instance Worker a poskytnutí odpovídající IMessageWriter implementace 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);
        }
    }
}

Pomocí vzoru DI služba pracovního procesu:

  • Nepoužívá konkrétní typ MessageWriter, pouze IMessageWriter rozhraní, které ho implementuje. To usnadňuje změnu implementace, kterou pracovní služba používá beze změny služby pracovního procesu.
  • Nevytvoří instanci MessageWriter. Instance je vytvořena kontejnerem DI.

Implementaci rozhraní je možné vylepšit pomocí integrovaného IMessageWriter rozhraní API pro protokolování:

namespace DependencyInjection.Example;

public class LoggingMessageWriter(
    ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
    public void Write(string message) =>
        logger.LogInformation("Info: {Msg}", message);
}

Aktualizovaná AddSingleton metoda zaregistruje novou IMessageWriter implementaci:

builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

Typ HostApplicationBuilder (builder) je součástí Microsoft.Extensions.Hosting balíčku NuGet.

LoggingMessageWriter závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>je služba poskytovaná architekturou.

Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.

Kontejner se ILogger<TCategoryName> vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.

Pomocí terminologie injektáže závislostí služba:

  • Je obvykle objekt, který poskytuje službu jiným objektům, jako IMessageWriter je služba.
  • Nesouvisí s webovou službou, i když služba může používat webovou službu.

Architektura poskytuje robustní systém protokolování. Implementace IMessageWriter uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které vyžaduje Worker registraci pouze jako hostované služby 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);
        }
    }
}

Pomocí předchozího kódu není nutné aktualizovat Program.cs, protože rozhraní poskytuje protokolování.

Pravidla zjišťování více konstruktorů

Pokud typ definuje více než jeden konstruktor, poskytovatel služeb má logiku pro určení konstruktoru, který se má použít. Konstruktor s nejvíce parametry, ve kterých jsou typy di-přeložitelné, je vybrán. Představte si následující ukázkovou službu jazyka C#:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(FooService fooService, BarService barService)
    {
        // omitted for brevity
    }
}

V předchozím kódu předpokládejme, že bylo přidáno protokolování a je možné ho přeložit od poskytovatele služeb, ale FooServiceBarService typy nejsou. Konstruktor s parametrem ILogger<ExampleService> se používá k překladu ExampleService instance. I když existuje konstruktor, který definuje více parametrů, FooService a BarService typy nejsou přeložitelné.

Pokud při zjišťování konstruktorů existuje nejednoznačnost, vyvolá se výjimka. Představte si následující ukázkovou službu jazyka C#:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Upozorňující

Kód ExampleService s nejednoznačnými parametry typu DI by vyvolal výjimku. Nedělejte to – účelem je ukázat, co je míněno "nejednoznačnými di-překladovými typy".

V předchozím příkladu existují tři konstruktory. První konstruktor je bez parametrů a nevyžaduje žádné služby od poskytovatele služeb. Předpokládejme, že do kontejneru DI byly přidány protokolování i možnosti a že se jedná o služby, které lze přeložit. Když se kontejner DI pokusí přeložit ExampleService typ, vyvolá výjimku, protože dva konstruktory jsou nejednoznačné.

Nejednoznačnosti se můžete vyhnout definováním konstruktoru, který místo toho přijímá oba typy překladu DI:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(
        ILogger<ExampleService> logger,
        IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Registrace skupin služeb pomocí rozšiřujících metod

Rozšíření Microsoftu používají konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME} rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. Například AddOptions metoda rozšíření zaregistruje všechny služby potřebné pro použití možností.

Služby poskytované architekturou

Při použití některého z dostupných vzorů hostitele nebo tvůrce aplikací se použijí výchozí hodnoty a rozhraní zaregistruje služby. Představte si některé z nejoblíbenějších vzorů pro hostitele a tvůrce aplikací:

Po vytvoření tvůrce z některého z těchto rozhraní API IServiceCollection má služby definované architekturou v závislosti na konfiguraci hostitele. Pro aplikace založené na šablonách .NET by architektura mohla zaregistrovat stovky služeb.

Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:

Typ služby Životnost
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Singleton
IHostApplicationLifetime Singleton
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Přechodná
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticListener Singleton
System.Diagnostics.DiagnosticSource Singleton

Životnost služeb

Služby je možné zaregistrovat v jedné z následujících životností:

Následující části popisují jednotlivé předchozí životnosti. Zvolte odpovídající dobu života pro každou zaregistrovanou službu.

Přechodná

Přechodné služby životnosti se vytvářejí při každém vyžádání z kontejneru služby. Chcete-li zaregistrovat službu jako přechodná, zavolejte AddTransient.

Vaplikacích Vzhledem k tomu, že se služby řeší a vytvářejí pokaždé, když se tato doba životnosti účtují a konstruují na jednotlivé požadavky. Další informace najdete v tématu Pokyny pro injektáž závislostí: Pokyny pro IDisposable pro přechodné a sdílené instance.

Rozsahem

U webových aplikací doba života s vymezeným oborem značí, že se služby vytvoří jednou podle požadavku klienta (připojení). Zaregistrujte služby s vymezeným oborem pomocí AddScoped.

V aplikacích, které zpracovávají požadavky, se na konci požadavku zlikvidují omezené služby.

Při použití Entity Framework Core AddDbContext metoda rozšíření ve výchozím nastavení registruje DbContext typy s vymezenou životností.

Poznámka:

Nevyřešujte službu s vymezeným oborem z jednohotonu a dávejte pozor, abyste to neprováděli nepřímo, například prostřednictvím přechodné služby. Při zpracování následných požadavků může dojít k nesprávnému stavu služby. Je v pořádku:

  • Vyřešte službu singleton z omezené nebo přechodné služby.
  • Vyřešte vymezenou službu z jiné služby s vymezeným oborem nebo přechodnou službou.

Ve výchozím nastavení vyvolá řešení služby z jiné služby s delší životností výjimku. Další informace najdete v tématu Ověření oboru.

Singleton

Služby s jednou životností se vytvářejí buď:

  • Při prvním vyžádání.
  • Vývojář při poskytování instance implementace přímo do kontejneru. Tento přístup je zřídka nutný.

Každý další požadavek implementace služby z kontejneru injektáže závislostí používá stejnou instanci. Pokud aplikace vyžaduje jednoúčelové chování, povolte kontejneru služby správu životnosti služby. Neimplementujte vzor návrhu singleton a poskytněte kód pro odstranění singletonu. Služby by nikdy neměly být uvolněny kódem, který službu přeložil z kontejneru. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.

Zaregistrujte jednoúčelové služby pomocí AddSingleton. Jednoúčelové služby musí být bezpečné pro přístup z více vláken a často se používají v bezstavových službách.

V aplikacích, které zpracovávají požadavky, se vyřadí jednoúčelové služby, když ServiceProvider se vyřadí při vypnutí aplikace. Vzhledem k tomu, že se paměť nevyvolá, dokud se aplikace nevypíná, zvažte použití paměti s jednou službou.

Metody registrace služby

Architektura poskytuje metody rozšíření registrace služeb, které jsou užitečné v konkrétních scénářích:

metoda Automatic (Automaticky)
objekt
K dispozici
Více
implementace
Předávání args
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()

Příklad:

services.AddSingleton<IMyDep, MyDep>();
Ano Ano No
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

Příklady:

services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep(99));
Ano Ano Yes
Add{LIFETIME}<{IMPLEMENTATION}>()

Příklad:

services.AddSingleton<MyDep>();
Yes No Ne
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})

Příklady:

services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep(99));
No Ano Yes
AddSingleton(new {IMPLEMENTATION})

Příklady:

services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));
No No Ano

Další informace o odstranění typu naleznete v části Vyřazení služeb .

Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .

K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton se jako typ služby volá dvakrát IMessageWriter . Druhé volání, které AddSingleton přepíše předchozí, když je vyřešeno jako IMessageWriter a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMessageWriter>. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím 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();

Předchozí vzorový zdrojový kód registruje dvě implementace 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);
    }
}

Definuje ExampleService dva parametry konstruktoru, jeden IMessageWritera .IEnumerable<IMessageWriter> Jediné IMessageWriter je poslední registrace implementace, zatímco IEnumerable<IMessageWriter> představuje všechny registrované implementace.

Architektura také poskytuje TryAdd{LIFETIME} rozšiřující metody, které zaregistrují službu pouze v případě, že ještě není zaregistrovaná implementace.

V následujícím příkladu volání AddSingleton registrů ConsoleMessageWriter jako implementace pro IMessageWriter. Volání TryAddSingleton nemá žádný účinek, protože IMessageWriter již má zaregistrovanou implementaci:

services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();

Nemá TryAddSingleton žádný vliv, protože už byl přidán a pokus se nezdaří. Tento výraz ExampleService by potvrdil následující:

public class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is ConsoleMessageWriter);
        Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
    }
}

Další informace naleznete v tématu:

Metody TryAddEnumerable(ServiceDescriptor) registrují službu pouze v případě, že ještě neexistuje implementace stejného typu. Více služeb je vyřešeno prostřednictvím IEnumerable<{SERVICE}>. Při registraci služeb přidejte instanci, pokud ještě nebyl přidán jeden ze stejných typů. Autoři knihoven se používají TryAddEnumerable k tomu, aby se zabránilo registraci více kopií implementace v kontejneru.

V následujícím příkladu první volání TryAddEnumerable registruje MessageWriter jako implementace pro IMessageWriter1. Druhý hovor se zaregistruje MessageWriter pro IMessageWriter2. Třetí volání nemá žádný účinek, protože IMessageWriter1 již má zaregistrovanou implementaci MessageWriter:

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>());

Registrace služby je obecně nezávislá na objednávce s výjimkou registrace více implementací stejného typu.

IServiceCollection je kolekce ServiceDescriptor objektů. Následující příklad ukazuje, jak zaregistrovat službu vytvořením a přidáním ServiceDescriptor:

string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
    typeof(IMessageWriter),
    _ => new DefaultMessageWriter(secretKey),
    ServiceLifetime.Transient);

services.Add(descriptor);

Integrované Add{LIFETIME} metody používají stejný přístup. Podívejte se například na zdrojový kód AddScoped.

Chování injektáže konstruktoru

Služby je možné vyřešit pomocí:

Konstruktory mohou přijímat argumenty, které nejsou poskytovány injektáží závislostí, ale argumenty musí přiřadit výchozí hodnoty.

Při řešení IServiceProvider služeb pomocí injektáže konstruktoru nebo ActivatorUtilitieskonstruktoru vyžaduje veřejný konstruktor.

Při řešení ActivatorUtilitiesslužeb pomocí injektáže konstruktoru vyžaduje, aby existoval pouze jeden použitelný konstruktor. Přetížení konstruktoru jsou podporována, ale pouze jedno přetížení může existovat, jehož argumenty mohou být splněny injektáž závislostí.

Ověření oboru

Když se aplikace spustí v Development prostředí a zavolá createApplicationBuilder k sestavení hostitele, výchozí poskytovatel služeb provede kontroly, aby ověřil, že:

  • Služby s vymezeným oborem se od poskytovatele kořenových služeb nepřeloží.
  • Služby s vymezeným oborem se nevkážou do singletonů.

Po zavolání se vytvoří BuildServiceProvider poskytovatel kořenové služby. Životnost poskytovatele kořenových služeb odpovídá životnosti aplikace, když poskytovatel začne s aplikací a je uvolněn při vypnutí aplikace.

Omezené služby jsou uvolněny kontejnerem, který je vytvořil. Pokud se v kořenovém kontejneru vytvoří vymezená služba, životnost služby se efektivně zvýší na singleton, protože je odstraněna pouze kořenovým kontejnerem, když se aplikace vypne. Ověřování rozsahů služby tyto situace zachytí, když BuildServiceProvider je volána.

Scénáře oborů

Je IServiceScopeFactory vždy registrován jako singleton, ale IServiceProvider může se lišit v závislosti na životnosti obsahující třídy. Pokud například přeložíte služby z oboru a každá z těchto služeb převezme IServiceProvidernějakou instanci, bude to instance s vymezeným oborem.

Pro dosažení rozsahu služeb v rámci implementací IHostedService, jako BackgroundServiceje například , nevkládat závislosti služby prostřednictvím injektáže konstruktoru. Místo toho vložte IServiceScopeFactory, vytvořte obor a pak přeložte závislosti z oboru tak, aby používala odpovídající dobu životnosti služby.

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);
                }
            }
        }
    }
}

V předchozím kódu, zatímco je aplikace spuštěná, služba na pozadí:

  • Závisí na závislosti IServiceScopeFactoryna .
  • Vytvoří pro IServiceScope překlad dalších služeb.
  • Řeší služby s vymezeným oborem pro spotřebu.
  • Pracuje na zpracování objektů a jejich následné předávání a nakonec je označí jako zpracované.

Z ukázkového zdrojového kódu si můžete prohlédnout, jak můžou implementace těžit z rozsahu IHostedService životnosti služby.

Služby s klíči

Od .NET 8 existuje podpora registrací služeb a vyhledávání na základě klíče, což znamená, že je možné zaregistrovat více služeb s jiným klíčem a tento klíč použít pro vyhledávání.

Představte si například případ, kdy máte různé implementace rozhraní IMessageWriter: MemoryMessageWriter a QueueMessageWriter.

Tyto služby můžete zaregistrovat pomocí přetížení metod registrace služby (vidět dříve), které podporují klíč jako parametr:

services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");

key Není omezen na string, může být libovolnýobject, pokud typ správně implementuje Equals.

V konstruktoru třídy, která používá IMessageWriter, přidáte FromKeyedServicesAttribute k zadání klíče služby k vyřešení:

public class ExampleService
{
    public ExampleService(
        [FromKeyedServices("queue")] IMessageWriter writer)
    {
        // Omitted for brevity...
    }
}

Viz také