Miroslav Holec
Premium

Hromadná registrace služeb do DI kontejneru v ASP.NET Core (Scrutor)

Miroslav Holec   7. listopadu 2019

Tento článek byl napsán v roce 2019. Vývojářské technologie se neustále inovují a článek již nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.

Dependency Injection je princip, který je v .NET Core aplikacích využíván od samého vzniku frameworku. Již po startu generického hosta je v DI kontejneru zaregistrována nezbytná řada služeb, které usnadňují vývojářům život. Další služby se registrují v rámci webhosta, aby usnadnily vývoj webových aplikací. Vlastní třídy si vývojář může registrovat dále sám. Jak je ale registrovat hromadně?

Motivace

V případě malých aplikací není problém napsat pár řádků kódu v ConfigureServices() metodě a registrovat si vlastní třídy ručně. Dokonce je to preferovaná varianta z hlediska čitelnosti i rychlosti. Pokud ale množství tříd naroste a máte v aplikaci v různých projektech hromadu services, repositářů, fasád a dalších specifických tříd, už není moc pohodlné všechno registrovat ručně. Jednak se na to zapomíná a jednak to znepřehledňuje Startup.cs.

Custom DI kontejnery jsou pomalé

Nejrychlejší řešení je použít další dependency injection container, který podporuje hromadnou registraci services. Potřebujete-li od DI kontejneru specifickou funkcionalitu, kterou výchozí MsDependencyInjection neumí, pak je toto výchozí volba. Jde-li vám však pouze o hromadnou registraci služeb při startu aplikace, je toto řešení nevhodné. Podle různých měření totiž všechny DI kontejnery významným způsobem zpomalují běh aplikace (ať už při registraci služeb nebo při resolve).

Unity, AutoFac, Simple Injector, StructureMap jsou cca o jeden řád pomalejší v komplexních naměřených scénářích (prepare, register, resolve). O dva až tři řády hůře vychází Windsor Castle a úplně nejhůře z výkonnostního pohledu zmíním Ninject, který je až o pět řádů pomalejší a pro rozsáhlejší aplikace tudíž prakticky nepoužitelný.

Když už DI kontejner, doporučuji zvolit AutoFac, který má v NET Core aplikacích skvělou podporu a dá se velmi pěkně registrovat. O registraci AutoFacu do .NET Core 3.0 pomocí ServiceProviderFactory jsem napsal článek nedávno.

Reflexe

Lepším řešením je vykašlat se na další DI kontejner a jednoduše si napsat pomocí reflexe vlastní assembly scanner a registrator. Většina vývojářů se sice v této oblasti vyřádí, ale také zpravidla vyprodukuje neefektivní nebo chybový kód. Navíc, proč psát něco, co už někdo napsal?

Konečně se dostáváme k tomu nejideálnějšímu přístupu. Tím je použití existujícího řešení založeného na efektivním proskenování assembly a registraci tříd dle definovaných pravidel. Existuje více projektů (a NuGet balíčků), které mi dlouhodobě nevyhovovaly zejména kvůli prazvláštním konvencím.

Scrutor

Před nějakým časem mě vývojáři z Oriflame Software seznámili s elegantní knihovnou Scrutor, která je speciálně napsaná na míru ServiceProvideru v .NET Core. V zásadě tak pouze používáte IServiceCollection extension metody, jako kdyby byla hromadná registrace tříd součástí .NET Core MSDI.

Ukázky

Scrutor je NuGet balíček, takže instalace je jednoduchá:

dotnet add package Scrutor

To je celé. Nikde není potřeba nic dalšího registrovat. NuGet balíček přidá extension metody nad třídou IServiceCollection, která je zpřístupněná ve Startup.cs metodě ConfigureServices()

public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{	        
             services.AddScoped<BlogRepository>();

	     // USE SCRUTOR HERE

Registrace může vypadat například takto:

services.Scan(scan => scan
    .FromAssemblyOf<ITransientService>()

        .AddClasses(classes => classes.AssignableTo<ITransientService>())
            .AsImplementedInterfaces()
            .WithTransientLifetime()

    	.AddClasses(classes => classes.AssignableTo<IScopedService>())
            .As<IScopedService>()
            .WithScopedLifetime()

Všimněte si, že v první části je nalezena assembly na základě ITransientService interface.

V další fázi jsou registrovány samotné třídy, které jsou přiřaditelné (realizované) interfacem ITransientService a jsou registrovány pro všechny interfaces, které realizují. Všechny registrované třídy mají lifestyle Transient.

Druhý příklad je podobný, ale registrace probíhá pouze proti IScopedService interface a to s lifestyle Scoped.

Závěr

Hledáte-li efektivní a elegantní cestu, jak registrovat více tříd v .NET Core aplikacích, Scrutor to umožňuje bez zbytečného zatížení aplikace dodatečným DI kontejnerem. Jeho velkou výhodou je čistá registrace skrze extension metody napsané nad IServiceCollection a dodržení konvencí, které známe z .NET Core MsDependencyInjection.