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:
- IServiceProvider Fogad egy konstruktort.
- A példány példányosítására használható ActivatorUtilities.CreateInstance a tárolón kívül, miközben 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.
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
ésTask
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 azIServiceProvider
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.
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.
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.
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.
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: true
BuildServiceProvider(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.
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
Visszajelzés
https://aka.ms/ContentUserFeedback.
Hamarosan elérhető: 2024-ben fokozatosan kivezetjük a GitHub-problémákat a tartalom visszajelzési mechanizmusaként, és lecseréljük egy új visszajelzési rendszerre. További információ:Visszajelzés küldése és megtekintése a következőhöz: