Share via


Függőséginjektálási irányelvek

Ez a cikk általános irányelveket és ajánlott eljárásokat tartalmaz a függőséginjektálás .NET-alkalmazásokban való implementálásához.

Tervezési szolgáltatások függőséginjektáláshoz

Függőséginjektálási szolgáltatások tervezésekor:

  • Kerülje az állapotalapú, statikus osztályokat és tagokat. Ne hozzon létre globális állapotot úgy, hogy az alkalmazásokat az önálló szolgáltatások használatára tervezi.
  • Kerülje a függő osztályok közvetlen példányosítását a szolgáltatásokban. A közvetlen példányosítás egy adott implementációhoz párosítja a kódot.
  • A szolgáltatások kicsi, jól faktorált és könnyen tesztelhetővé tétele.

Ha egy osztálynak sok injektált függősége van, annak jele lehet, hogy az osztály túl sok felelősségi körrel rendelkezik, és megsérti az egységes felelősség elvét (SRP). Próbálja meg újra bontani az osztályt úgy, hogy egyes feladatait új osztályokba helyezi át.

Szolgáltatások elidegenítése

A tároló felelős a létrehozott típusok törléséért, és meghívja Dispose a IDisposable példányokat. A tárolóból feloldott szolgáltatásokat a fejlesztőnek soha nem szabad megsemmisítenie. Ha egy típus vagy gyár egyetlentonként van regisztrálva, a tároló automatikusan megsemmisíti a singletont.

A következő példában a szolgáltatástároló hozza létre a szolgáltatásokat, és automatikusan megsemmisíti:

namespace ConsoleDisposable.Example;

public sealed class TransientDisposable : IDisposable
{
    public void Dispose() => Console.WriteLine($"{nameof(TransientDisposable)}.Dispose()");
}

Az előző eldobható célja, hogy egy átmeneti élettartam.

namespace ConsoleDisposable.Example;

public sealed class ScopedDisposable : IDisposable
{
    public void Dispose() => Console.WriteLine($"{nameof(ScopedDisposable)}.Dispose()");
}

Az előző eldobható rendeltetése, hogy hatókörön belüli élettartamú legyen.

namespace ConsoleDisposable.Example;

public sealed class SingletonDisposable : IDisposable
{
    public void Dispose() => Console.WriteLine($"{nameof(SingletonDisposable)}.Dispose()");
}

Az előző eldobható célja, hogy egyton élettartam.

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

A hibakeresési konzol a következő mintakimenetet jeleníti meg a futtatás után:

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

A szolgáltatástároló által nem létrehozott szolgáltatások

Tekintse meg az alábbi kódot:

// Register example service in IServiceCollection
builder.Services.AddSingleton(new ExampleService());

A fenti kód a következőket végzi el:

  • A ExampleService példányt nem a szolgáltatástároló hozza létre.
  • A keretrendszer nem rendelkezik automatikusan a szolgáltatásokkal.
  • A szolgáltatások eltávolításáért a fejlesztő felel.

IDisposable útmutató átmeneti és megosztott példányokhoz

Átmeneti, korlátozott élettartam

Forgatókönyv

Az alkalmazásnak átmeneti élettartamú példányra van szüksége IDisposable az alábbi forgatókönyvek egyikéhez:

  • A példány feloldása a gyökérhatókörben (gyökértárolóban) történik.
  • A példányt a hatókör vége előtt kell megsemmisíteni.

Megoldás

A gyári minta használatával hozzon létre egy példányt a szülő hatókörén kívül. Ebben az esetben az alkalmazás általában rendelkezik egy Create olyan módszerrel, amely közvetlenül meghívja a végső típus konstruktorát. Ha a végső típus más függőségekkel is rendelkezik, a gyár a következőt teheti:

Megosztott példány, korlátozott élettartam

Forgatókönyv

Az alkalmazásnak több szolgáltatásban is meg kell osztania egy megosztott IDisposable példányt, de a IDisposable példánynak korlátozott élettartamúnak kell lennie.

Megoldás

Regisztrálja a példányt egy hatókörrel rendelkező élettartammal. Új IServiceScopeFactory.CreateScopeIServiceScope. A hatókör használatával szerezze IServiceProvider be a szükséges szolgáltatásokat. Helyezze el a hatókört, ha már nincs rá szükség.

Általános IDisposable irányelvek

  • Ne regisztráljon IDisposable példányokat átmeneti élettartammal. Használja inkább a gyári mintát.
  • Ne oldjon fel IDisposable átmeneti vagy hatókörön belüli élettartamú példányokat a gyökérhatókörben. Ez alól az egyetlen kivétel, ha az alkalmazás létrehoz/újra létrehoz és megsemmisít IServiceProvider, de ez nem ideális minta.
  • IDisposable A függőség dián keresztül történő fogadásához nem szükséges, hogy a fogadó megvalósítsa IDisposable magát. A függőség fogadójának IDisposable nem szabad meghívnia Dispose ezt a függőséget.
  • Hatókörök használatával szabályozhatja a szolgáltatások élettartamát. A hatókörök nem hierarchikusak, és nincs különleges kapcsolat a hatókörök között.

Az erőforrás-törléssel kapcsolatos további információkért lásd: Metódus implementálása Dispose vagy Metódus implementálásaDisposeAsync. Emellett vegye figyelembe a tárolóforgatókönyv által rögzített eldobható átmeneti szolgáltatásokat is, mivel az erőforrás-törléshez kapcsolódik.

Alapértelmezett szolgáltatástároló cseréje

A beépített szolgáltatástároló a keretrendszer és a legtöbb fogyasztói alkalmazás igényeinek kielégítésére lett kialakítva. Javasoljuk, hogy használja a beépített tárolót, kivéve, ha olyan funkcióra van szüksége, amelyet nem támogat, például:

  • Tulajdonságinjektálás
  • Injektálás név alapján (csak.NET 7 és korábbi verziók esetén). További információ: Keyed services.)
  • Gyermektárolók
  • Egyéni élettartam-kezelés
  • Func<T> lusta inicializálás támogatása
  • Konvencióalapú regisztráció

A következő külső tárolók használhatók ASP.NET Core-alkalmazásokkal:

Menetbiztonság

Szálbiztos singleton-szolgáltatások létrehozása. Ha egy singleton szolgáltatás függőségben áll egy átmeneti szolgáltatással, az átmeneti szolgáltatás szálbiztonságot is igényelhet attól függően, hogy az adott szolgáltatás hogyan használja azt.

A singleton szolgáltatás gyári metódusának, például az AddSingleton TService> (IServiceCollection, Func<IServiceProvider,TService>) második argumentumának nem kell szálbiztosnak<lennie. A típus (static) konstruktorhoz hasonlóan garantáltan csak egyszer hívható meg egyetlen szál.

Ajánlások

  • async/await és Task a alapú szolgáltatásfeloldás nem támogatott. Mivel a C# nem támogatja az aszinkron konstruktorokat, a szolgáltatás szinkron feloldása után használjon aszinkron metódusokat.
  • Kerülje az adatok és konfigurációk közvetlen tárolását a szolgáltatástárolóban. Például a felhasználó bevásárlókocsiját általában nem szabad hozzáadni a szolgáltatástárolóhoz. A konfigurációnak a beállítási mintát kell használnia. Hasonlóképpen kerülje az "adattulajdonos" objektumokat, amelyek csak egy másik objektumhoz való hozzáférés engedélyezéséhez léteznek. Jobb, ha a tényleges elemet a DI-en keresztül kéri le.
  • Kerülje a szolgáltatásokhoz való statikus hozzáférést. Kerülje például az IApplicationBuilder.ApplicationServices statikus mezőként vagy tulajdonságként való rögzítését máshol való használatra.
  • Tartsa a DI-gyárakat gyorsan és szinkronban.
  • Kerülje a szolgáltatáskereső minta használatát. Ne hívjon GetService meg például egy szolgáltatáspéldány beszerzéséhez, ha használhatja helyette a DI-t.
  • Egy másik szolgáltatáskereső változat, amely elkerülhető, egy olyan gyár injektálása, amely futásidőben oldja fel a függőségeket. Mindkét gyakorlat keveri az Inversion of Control stratégiákat.
  • A szolgáltatások konfigurálásakor kerülje a BuildServiceProvider hívásokat. A hívás BuildServiceProvider általában akkor fordul elő, ha a fejlesztő egy másik szolgáltatás regisztrálásakor fel szeretne oldani egy szolgáltatást. Ehelyett használjon túlterhelést, amely tartalmazza ezt az IServiceProvider okból.
  • Az eldobható átmeneti szolgáltatásokat a tároló rögzíti az ártalmatlanításhoz. Ez memóriaszivárgássá alakulhat, ha a legfelső szintű tárolóból oldják fel.
  • Engedélyezze a hatókör-ellenőrzést annak érdekében, hogy az alkalmazás ne rendelkezzen hatóköralapú szolgáltatásokat rögzítő egytonnával. További információ: Hatókör érvényesítése.

A javaslatokhoz hasonlóan olyan helyzetek is előfordulhatnak, amikor figyelmen kívül kell hagyni egy javaslatot. A kivételek ritkák, többnyire különleges esetek a keretrendszeren belül.

A DI a statikus/globális objektumhozzáférési minták alternatívája . Előfordulhat, hogy nem tudja kihasználni a DI előnyeit, ha statikus objektumhozzáféréssel keveri.

Példa anti-minták

A cikkben szereplő irányelvek mellett számos antimintát is el kell kerülnie. Ezek közül az anti-minták közül néhány a futtatókörnyezetek fejlesztésének elsajátítása.

Figyelmeztetés

Ezek például anti-minták, ne másolja a kódot, ne használja ezeket a mintákat, és kerülje ezeket a mintákat minden áron.

Tároló által rögzített eldobható átmeneti szolgáltatások

Ha átmeneti szolgáltatásokat regisztrál, amelyek implementálva IDisposablevannak, a DI-tároló alapértelmezés szerint ezekre a hivatkozásokra marad, és nem Dispose() ezekre, amíg az alkalmazás leáll, ha az alkalmazás feloldotta őket a tárolóból, vagy amíg a hatókört el nem távolítja, ha azok feloldva lettek egy hatókörből. Ez memóriaszivárgássá alakulhat, ha a tárolószintről feloldják.

Anti-pattern: Transient disposables without dispose. Do not copy!

Az előző antimintában 1000 ExampleDisposable objektumot példányosítunk és gyökereztetünk. Ezek a példányok mindaddig nem lesznek megsemmisítve, amíg a serviceProvider példány el nem kerül.

A memóriaszivárgások hibakeresésével kapcsolatos további információkért lásd : Memóriaszivárgás hibakeresése a .NET-ben.

Az Async DI-gyárak holtpontot okozhatnak

A "DI-gyárak" kifejezés a híváskor Add{LIFETIME}létező túlterhelési módszerekre utal. Vannak túlterhelések, amelyek Func<IServiceProvider, T> elfogadják, hogy hol T van regisztrálva a szolgáltatás, és a paraméter neve implementationFactory. Ez implementationFactory lehet lambda kifejezés, helyi függvény vagy metódus. Ha a gyár aszinkron, és ön használja Task<TResult>.Result, az holtpontot okoz.

Anti-pattern: Deadlock with async factory. Do not copy!

Az előző kódban a implementationFactory rendszer egy lambda kifejezést ad meg, amelyben a törzs egy Task<Bar> visszatérési metódust hív Task<TResult>.Result meg. Ez holtpontot okoz. A GetBarAsync metódus egyszerűen emulál egy aszinkron munkaműveletet Task.Delay, majd meghívja GetRequiredService<T>(IServiceProvider)őket.

Anti-pattern: Deadlock with async factory inner issue. Do not copy!

Az aszinkron útmutatással kapcsolatos további információkért lásd : Aszinkron programozás: Fontos információk és tanácsok. További információ a holtpontok hibakereséséről: Holtpont hibakeresése a .NET-ben.

Ha ezt az antimintát futtatja, és a holtpont bekövetkezik, megtekintheti a Visual Studio Párhuzamos verem ablakából várakozó két szálat. További információ: Szálak és feladatok megtekintése a Párhuzamos verem ablakban.

Kötött függőség

A "kötött függőség" kifejezést Mark Seemann alkotta meg, és a szolgáltatás élettartamának helytelen konfigurálására utal, ahol egy hosszabb élettartamú szolgáltatás rövidebb élettartamú szolgáltatást tart fogva.

Anti-pattern: Captive dependency. Do not copy!

Az előző kódban egyetlentonként van regisztrálva, Foo és Bar hatókörrel rendelkezik – amely a felületen érvényesnek tűnik. Fontolja meg azonban a .Foo

namespace DependencyInjection.AntiPatterns;

public class Foo(Bar bar)
{
}

Az Foo objektumhoz objektumra van szükség Bar , és mivel Foo egy egytonos, és Bar hatókörrel rendelkezik – ez egy helytelen konfiguráció. Ahogy az is, Foo csak egyszer lenne példányosítva, és az élettartamát is megtartaná Bar , ami hosszabb, mint a kívánt hatókörön belüli élettartam Bar. Érdemes megfontolni a hatókörök érvényesítését a validateScopes: trueBuildServiceProvider(IServiceCollection, Boolean). A hatókörök ellenőrzésekor a következőhöz hasonló üzenet jelenik meg InvalidOperationException : "Nem használható a "Bar" hatókörű szolgáltatás a "Foo" egyetlentonnájából.

További információ: Hatókör érvényesítése.

Hatókörön belüli szolgáltatás önállóan

Hatóköralapú szolgáltatások használata esetén, ha nem hoz létre hatókört vagy egy meglévő hatókörön belül, a szolgáltatás egyetlen tartománysá válik.

Anti-pattern: Scoped service becomes singleton. Do not copy!

Az előző kódban Bar a rendszer egy IServiceScopehelyesen megadott kódon belül kéri le. Az anti-minta a hatókörön kívüli lekérés Bar , a változó neve avoid pedig annak megjelenítésére, hogy melyik példalekérés helytelen.

Lásd még