Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
Ez a cikk általános irányelveket és ajánlott eljárásokat tartalmaz a függőséginjektálás (DI) .NET-alkalmazásokban való végrehajtá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; inkább tervezze az alkalmazásokat singleton típusú szolgáltatások használatára.
- 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 megszüntetése
A tároló felelős a létrehozott típusok törléséért, és meghívja a Dispose metódust a IDisposable példányokon. A tárolóból feloldott szolgáltatásokat a fejlesztőnek soha nem szabad megsemmisítenie. Ha egy típus vagy gyár singletonként van regisztrálva, a konténer 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ó termék rendeltetésszerűen átmeneti élettartammal rendelkezik.
namespace ConsoleDisposable.Example;
public sealed class ScopedDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(ScopedDisposable)}.Dispose()");
}
Az előző eldobható eszköz rendeltetése, hogy egy meghatározott hatókör mellett működjön.
namespace ConsoleDisposable.Example;
public sealed class SingletonDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(SingletonDisposable)}.Dispose()");
}
Az előző eldobható elem célja, hogy egyszeri élettartammal rendelkezzen.
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());
Az előző kódban:
- A
ExampleServicepé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.
Solution
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:
- Az IServiceProvider fogadásra kerül a konstruktorban.
- A példányosításra használja a ActivatorUtilities.CreateInstance-t a tárolón kívül, ugyanakkor a tárolót használja a függőségeihez.
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.
Solution
Regisztrálja a példányt hatókörhöz kötött élettartammal. Használja a IServiceScopeFactory.CreateScope-t egy új IServiceScope létrehozásához. A hatókör használatával érje el 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ártási mintázatot, hogy a megoldott szolgáltatást manuálisan meg lehessen semmisíteni, ha már nincs használatban.
- 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étrehozza, újra létrehozza és megsemmisíti IServiceProvider, de ez nem ideális megoldás.
- 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
- 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:
Szálbiztonság
Szálbiztos singleton-szolgáltatások létrehozása. Ha egy egyszeri szolgáltatás függőségben van 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<)>nem kell szálbiztosnaklennie. A típus (static) konstruktorhoz hasonlóan garantáltan csak egyszer hívható meg egyetlen szál.
Emellett a szolgáltatásoknak a beépített .NET-függőséginjektálási tárolóból való feloldásának folyamata szálbiztos.
Miután elkészült egy IServiceProvider vagy IServiceScope, biztonságosan feloldhatja a szolgáltatásokat több szálon egyszerre.
Megjegyzés:
Maga a DI-tároló szálbiztonsága csak a szolgáltatások biztonságos felépítését és feloldását garantálja. Nem teszi szálbiztossá a feloldott szolgáltatáspéldányokat. Bármely szolgáltatás, amely megosztott módosítható állapotot tartalmaz (különösen az egyedülálló szolgáltatások), megfelelő szinkronizálási logikát kell hogy alkalmazzon, ha egyidejűleg férnek hozzá.
Recommendations
-
async/awaitésTaskalapú 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 az opciós 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. Például ne rögzítse IApplicationBuilder.ApplicationServices statikus mezőként vagy tulajdonságként máshol való használatra.
- Tartsa a DI-gyárakat gyorsan és szinkronban működővé.
- 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 egy túlterhelést, amely tartalmazza aIServiceProvidererre a célra. - Az átmeneti eldobható szolgáltatásokat a konténer 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 singletonokkal, amelyek hatókörű szolgáltatásokat rögzítenek. További információ: Hatókör érvényesítése.
- Csak az önálló élettartamot használja a saját állapotú szolgáltatásokhoz, amelyek létrehozása költséges vagy globálisan megosztott. Ne használjon egyszeri élettartamot olyan szolgáltatásokhoz, amelyek nem rendelkeznek állapottal. A legtöbb .NET IoC-tároló alapértelmezett hatókörként az "Átmeneti" értéket használja. A singletonokkal kapcsolatos szempontok és hátrányok:
- Menetbiztonság: A szingletont szálbiztonságos módon kell megvalósítani.
- Összekapcsolás: Összekapcsolhatja az egyébként nem kapcsolódó kéréseket.
- Tesztelési kihívások: A megosztott állapot és az összekapcsolás megnehezítheti az egységtesztelést.
- Memóriahatás: Egy singleton nagy objektum gráfot tarthat életben a memóriában az alkalmazás teljes élettartama alatt.
- Hibatűrés: Ha egy singleton vagy a függőségi fa bármely része meghibásodik, nem tud könnyen helyreállítani.
- Konfiguráció újratöltése: A singletonok általában nem támogatják a konfigurációs értékek "gyakori újratöltését".
- Hatókörszivárgás: Egy singleton véletlenül befogadhat hatókörrel rendelkező vagy átmeneti függőségeket, hatékonyan singletonná előléptetve őket, és nem kívánt mellékhatásokat okozhat.
- Inicializálási többletterhelés: A szolgáltatás feloldásakor az IoC-tárolónak meg kell keresnie az egypéldányos példányt. Ha még nem létezik, akkor szálbiztos módon kell létrehoznia. Ezzel szemben egy állapot nélküli átmeneti szolgáltatás nagyon olcsó létrehozni és megsemmisíteni.
Mint minden javaslatkészlet, olyan helyzetek is előfordulhatnak, amikor figyelmen kívül kell hagyni egy javaslatot. A kivételek ritkák, és többnyire a keretrendszeren belüli különleges esetek.
A DI a statikus/globális objektumhozzáférési minták alternatívája . Előfordulhat, hogy nem ismeri fel a DI előnyeit, ha statikus objektumhozzáféréssel keveri.
Példa ellenminták
A cikkben szereplő irányelvek mellett számos antimintát is el kell kerülnie. Ezeknek az anti-mintáknak a közül néhány a futtatókörnyezetek fejlesztése során szerzett tapasztalat.
Figyelmeztetés
Ezek példa ellenminták. Ne másolja a kódot, ne használja ezeket a mintákat, és minden áron kerülje ezeket a mintákat.
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 ezeket a hivatkozásokat tárolja. Nem távolítja el őket, amíg a tárolót el nem távolítja, amikor az alkalmazás leáll, ha feloldották őket a tárolóból, vagy amíg a hatókört el nem távolítja, ha egy hatókörből oldották fel őket. A memóriaszivárgás akkor következhet be, ha megoldás a konténer szintjén történik.
Az előző antimintában 1000 ExampleDisposable objektumot példányosítunk és gyökereztetünk. A példányok 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 túlterheléses módszerekre utal, amelyek akkor léteznek, amikor a Add{LIFETIME} hívás történik. Olyan túlterhelések vannak, amelyek elfogadják a Func<IServiceProvider, T>-t, ahol a T a regisztrált 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 használja Task<TResult>.Result, holtpontot okoz.
Az előző kódban a implementationFactory megkap egy lambda kifejezést, amelyben a törzs egy Task<TResult>.Result függvényt hív meg egy Task<Bar> visszaadó metódus során. Ez holtpontot okoz. A GetBarAsync metódus egyszerűen emulál egy aszinkron munkaműveletet Task.Delay, majd meghívja GetRequiredService<T>(IServiceProvider).
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 holtpont alakul ki, a Visual Studio Párhuzamos veremablakából megtekintheti a várakozó két szálat. További információ: Szálak és feladatok megtekintése a Párhuzamos veremek ablakban.
Kötött függőség
A Mark Seemann által létrehozott "kötött függőség" kifejezés a szolgáltatási élettartamok helytelen konfigurálására utal, ahol egy hosszabb élettartamú szolgáltatás rövidebb élettartamú szolgáltatást tart fogva.
Az előző kódban Foo szingletontként van regisztrálva, míg Bar hatókörrel rendelkezik – amely első ránézésre érvényesnek tűnik. Azonban fontolja meg a Foo végrehajtását.
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ó. Az aktuális állapot szerint a Foo csak egyszer van példányosítva, és az élettartama alatt megtartja a Bar-et, ami hosszabb, mint a Bar tervezett hatókörön belüli élettartama. Fontolja meg a hatókörök érvényesítését azáltal, hogy a validateScopes: true-t a BuildServiceProvider(IServiceCollection, Boolean)-nek adja át. A hatókörök érvényesítésekor megjelenik egy InvalidOperationException üzenet, amely hasonló a következőhöz: "Nem lehet a 'Bar' hatókörű szolgáltatást a 'Foo' singleton-ból felhasználni."
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örrel rendelkező szolgáltatások használata esetén, ha nem hoz létre hatókört vagy meglévő hatókörön belül, a szolgáltatás egyetlen tartománysá válik.
Az előző kódban a Bar egy IServiceScope-en belül van lekérve, ami helyes. Az anti-minta a hatókörön kívüli lekérés Bar, és a változó neve avoid, hogy megmutassa, melyik példalekérés helytelen.