Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek obsahuje obecné pokyny a osvědčené postupy pro implementaci injektáže závislostí v aplikacích .NET.
Návrh služeb pro injektáž závislostí
Při navrhování služeb pro injektování závislostí:
- Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
- Vyhněte se přímému vytváření instancí závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
- Udělejte služby malými, dobře strukturovanými a snadno otestovatelnými.
Pokud má třída mnoho injektovaných závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu Single Responsibility Principle (SRP). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd.
Likvidace služeb
Kontejner je zodpovědný za vyčištění typů, které vytváří, a volá Dispose na IDisposable instancích. Služby získané z kontejneru by nikdy neměly být likvidovány vývojářem. Pokud je typ nebo továrna registrován jako singleton, kontejner automaticky odstraní singleton.
V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky odstraněny:
namespace ConsoleDisposable.Example;
public sealed class TransientDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(TransientDisposable)}.Dispose()");
}
Předchozí jednorázový výrobek je určen k krátkodobé životnosti.
namespace ConsoleDisposable.Example;
public sealed class ScopedDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(ScopedDisposable)}.Dispose()");
}
Předchozí věc na jedno použití je určená pro určitou dobu použití.
namespace ConsoleDisposable.Example;
public sealed class SingletonDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(SingletonDisposable)}.Dispose()");
}
Předchozí jednorázový je určen k singletové životnosti.
using ConsoleDisposable.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<TransientDisposable>();
builder.Services.AddScoped<ScopedDisposable>();
builder.Services.AddSingleton<SingletonDisposable>();
using IHost host = builder.Build();
ExemplifyDisposableScoping(host.Services, "Scope 1");
Console.WriteLine();
ExemplifyDisposableScoping(host.Services, "Scope 2");
Console.WriteLine();
await host.RunAsync();
static void ExemplifyDisposableScoping(IServiceProvider services, string scope)
{
Console.WriteLine($"{scope}...");
using IServiceScope serviceScope = services.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
_ = provider.GetRequiredService<TransientDisposable>();
_ = provider.GetRequiredService<ScopedDisposable>();
_ = provider.GetRequiredService<SingletonDisposable>();
}
Po spuštění konzoly ladění se zobrazí následující ukázkový výstup:
Scope 1...
ScopedDisposable.Dispose()
TransientDisposable.Dispose()
Scope 2...
ScopedDisposable.Dispose()
TransientDisposable.Dispose()
info: Microsoft.Hosting.Lifetime[0]
Application started.Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: .\configuration\console-di-disposable\bin\Debug\net5.0
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
SingletonDisposable.Dispose()
Služby, které se nevytvořily kontejnerem služby
Uvažujte následující kód:
// Register example service in IServiceCollection
builder.Services.AddSingleton(new ExampleService());
V předchozím kódu:
- Instance
ExampleService
není vytvořena kontejnerem služby. - Architektura nelikviduje služby automaticky.
- Vývojář zodpovídá za likvidaci služeb.
Pokyny pro IDisposable pro přechodné a sdílené instance
Přechodná, omezená životnost
Scénář
Aplikace vyžaduje IDisposable instanci s přechodnou životností pro některý z následujících scénářů:
- Instance je vyřešena v kořenovém rozsahu (kořenovém kontejneru).
- Instance by měla být odstraněna před ukončením rozsahu.
Řešení
Použijte vzor továrny k vytvoření instance v oblasti mimo nadřazený obor. V takovém případě by aplikace měla obecně metodu Create
, která volá přímo konstruktor konečného typu. Pokud konečný typ obsahuje další závislosti, může továrna:
- Přijmout IServiceProvider ve svém konstruktoru.
- Použijte ActivatorUtilities.CreateInstance pro vytvoření instance mimo kontejner a využijte kontejner pro její závislosti.
Sdílená instance, omezená životnost
Scénář
Aplikace vyžaduje sdílenou IDisposable instanci napříč několika službami, ale IDisposable instance by měla mít omezenou životnost.
Řešení
Zaregistrujte instanci s vymezenou životností. Použijte IServiceScopeFactory.CreateScope k vytvoření nového IServiceScope. Použijte obor k získání požadovaných služeb IServiceProvider. Zrušte rozsah, pokud už ho nepotřebujete.
Obecné IDisposable
pokyny
- Nezaregistrujte IDisposable instance s přechodnou životností. Místo toho použijte vzor továrny.
- Nevyřešujte IDisposable instance s přechodnou nebo vymezenou životností v kořenovém oboru. Jedinou výjimkou je, když aplikace vytvoří/znovu vytvoří a odstraní IServiceProvider, ale nejedná se o ideální vzorec.
- Příjem závislosti prostřednictvím DI nevyžaduje, aby příjemce implementoval IDisposable sám. Příjemce IDisposable závislosti by neměl volat Dispose na danou závislost.
- Používejte rozsahy k řízení životnosti služeb. Obory nejsou hierarchické a mezi obory neexistuje žádné zvláštní propojení.
Další informace o vyčištění prostředků naleznete v tématu Implementace Dispose
metody nebo Implementace DisposeAsync
metody. Kromě toho zvažte jednorázové přechodné služby zachycené kontejnerem, protože souvisí s vyčištěním prostředků.
Nahrazení výchozího kontejneru služby
Integrovaný kontejner služby je navržený tak, aby sloužil potřebám architektury a většiny uživatelských aplikací. Doporučujeme používat integrovaný kontejner, pokud nepotřebujete konkrétní funkci, kterou nepodporuje, například:
- Injektáž vlastností
- Injektáž založená na názvu (jenom .NET 7 a starší verze). Další informace najdete v tématu Služby s klíči.)
- Podřízené kontejnery
- Správa vlastního životního cyklu
-
Func<T>
podpora opožděné inicializace - Registrace na základě konvence
Následující kontejnery třetích stran je možné použít s aplikacemi ASP.NET Core:
Bezpečnost vlákna
Vytvořte jednoinstanční služby s bezpečným přístupem z více vláken. Pokud má singletonová služba závislost na přechodné službě, může přechodná služba také vyžadovat zabezpečení vláken v závislosti na tom, jak ji singletonová služba používá.
Metoda továrny jednoúčelové služby, například druhý argument AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), nemusí být bezpečný pro přístup z více vláken. Stejně jako konstruktor typu (static
) je zaručeno, že bude volán pouze jednou jedním vláknem.
Doporučení
-
async/await
aTask
založené na službách řešení není podporováno. Vzhledem k tomu, že jazyk C# nepodporuje asynchronní konstruktory, po synchronním vyřešení služby používejte asynchronní metody. - Vyhněte se ukládání dat a konfigurace přímo v kontejneru služby. Nákupní košík uživatele by například neměl být obvykle přidán do kontejneru služeb. Konfigurace by měla používat vzor možností. Podobně se vyhněte objektům "držitel dat", které existují pouze pro povolení přístupu k jinému objektu. Je lepší požádat o skutečnou položku prostřednictvím DI.
- Vyhněte se statickému přístupu ke službám. Vyhněte se například zachycení IApplicationBuilder.ApplicationServices jako statického pole nebo vlastnosti pro použití jinde.
- Udržujte DI továrny rychlé a synchronní.
- Nepoužívejte vzor lokátor služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci.
- Další variantou lokátoru služeb, které byste se měli vyhnout, je injektovat továrnu, která řeší závislosti při spuštění. Obě tyto postupy se směšují s inverzí strategií řízení .
- Vyhněte se volání BuildServiceProvider při konfiguraci služeb. Volání
BuildServiceProvider
obvykle nastane, když vývojář chce vyřešit službu při registraci jiné služby. Místo toho z tohoto důvodu použijte přetížení, které obsahujeIServiceProvider
. - Jednorázové přechodné služby jsou zachyceny kontejnerem pro odstranění. To může vést k úniku paměti, pokud je vyřešeno z kontejneru nejvyšší úrovně.
- Povolte ověřování oboru, pro zajištění, že aplikace nemá singletony, které zachytávají služby s vymezeným oborem. Další informace najdete v tématu Validace rozsahu.
Stejně jako u všech sad doporučení můžete narazit na situace, kdy se vyžaduje ignorování doporučení. Výjimky jsou vzácné, většinou zvláštní případy v rámci samotného rámce.
Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.
Příklady antivzorů
Kromě pokynů v tomto článku existuje několik anti-vzorů , kterým byste se měli vyhnout. Některé z těchto anti-vzorů jsou poznatky získané z vývoje samotných runtime.
Varování
Jedná se o ukázkové anti-vzory, nekopírujte kód, nepoužívejte tyto vzory a vyhněte se těmto vzorům za všechny náklady.
Jednorázové přechodné služby zachycené kontejnerem
Když zaregistrujete přechodné služby, které implementují IDisposable, kontejner DI bude ve výchozím nastavení uchovávat tyto odkazy a ne Dispose() z nich, dokud kontejner nebude zlikvidován, když se aplikace zastaví, pokud byly vyřešeny z kontejneru, nebo dokud se neukončí rozsah, pokud byly vyřešeny z oboru. To se může změnit na únik paměti, pokud je vyřešeno na úrovni kontejneru.
Ve předchozím anti-příkladu se vytvoří instance 1 000 ExampleDisposable
objektů a jsou ukotveny. Nebudou odstraněny, dokud serviceProvider
instance nebude odstraněna.
Další informace o ladění úniků paměti naleznete v tématu Ladění úniku paměti v .NET.
Asynchronní továrny DI můžou způsobit zablokování
Termín "DI factories" odkazuje na metody přetížení, které existují při volání Add{LIFETIME}
. Existují přetížení, která přijímají parametr Func<IServiceProvider, T>
, kde je T
služba, a parametr se nazývá implementationFactory
. Dá implementationFactory
se zadat jako výraz lambda, místní funkce nebo metoda. Pokud je továrna asynchronní a použijete Task<TResult>.Result, způsobí to zablokování.
V předchozím kódu je implementationFactory
přiřazen výraz lambda, v němž tělo volá Task<TResult>.Result na metodě vracející Task<Bar>
. To způsobí zablokování. Metoda GetBarAsync
jednoduše emuluje asynchronní pracovní operaci s Task.Delay, a pak volá GetRequiredService<T>(IServiceProvider).
Další informace o asynchronním poradenství najdete v tématu Asynchronní programování: Důležité informace a rady. Další informace o ladění zablokování naleznete v tématu Jak ladit zablokování v .NET.
Když spouštíte tento nevhodný vzor a dojde k zablokování, můžete z okna Paralelní zásobníky sady Visual Studio zobrazit dvě čekající vlákna. Další informace najdete v tématu Zobrazení vláken a úloh v okně Paralelní zásobníky.
Omezená závislost
Pojem "závislost v držení" byl vytvořen Markem Seemannem a odkazuje na nesprávnou konfiguraci životnosti služeb, kde služba s delší životností drží službu s kratší životností v zajetí.
V předchozím kódu je Foo
registrován jako singleton a Bar
je vymezený, což se na první pohled zdá být platné. Zvažte však implementaci Foo
.
namespace DependencyInjection.AntiPatterns;
public class Foo(Bar bar)
{
}
Objekt Foo
vyžaduje objekt Bar
, a jelikož Foo
je singleton a Bar
je vymezený – jedná se o chybnou konfiguraci. V současné podobě by byla instance Foo
vytvořena pouze jednou a uchovávala Bar
po celou dobu své existence, která je delší než zamýšlená životnost Bar
. Měli byste zvážit ověření oborů předáním validateScopes: true
do BuildServiceProvider(IServiceCollection, Boolean). Když ověříte obory, dostanete InvalidOperationException se zprávou podobnou této: "Nelze použít vymezenou službu 'Bar' z singletonu 'Foo'.".
Další informace najdete v tématu Validace rozsahu.
Služba s vymezeným oborem jako singleton
Pokud při použití služeb s vymezeným oborem nevytvářete obor nebo v rámci existujícího oboru , stane se služba jediným oborem.
V předchozím kódu se Bar
načte uvnitř IServiceScope, což je správné. Anti-patternem je načítání Bar
mimo obor a proměnná je pojmenována jako avoid
k označení, které ukázkové načítání je nesprávné.