ASP.NET Core Dependency Injection Bevált gyakorlatok, tippek és trükkök

Ebben a cikkben megosztom tapasztalataimat és javaslataimat a Függőség-befecskendezés használatáról az ASP.NET Core alkalmazásokban. Ezen elvek mögött a motiváció áll;

  • A szolgáltatások és azok függőségeinek hatékony megtervezése.
  • A többszálú kérdések megelőzése.
  • A memóriaszivárgások megelőzése.
  • A lehetséges hibák megelőzése.

Ez a cikk feltételezi, hogy már ismeri a Dependency Injection és az ASP.NET Core alapszinteket. Ha nem, kérjük, először olvassa el az ASP.NET Core Dependency Injection dokumentációját.

alapjai

Konstruktor befecskendezése

A konstruktor befecskendezésével deklarálható és kiszámítható egy szolgáltatás függvénye a szolgáltatás építésétől. Példa:

nyilvános osztályú ProductService
{
    magán readonly IProductRepository _productRepository;
    nyilvános ProductService (IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
    public void törlés (int id)
    {
        _productRepository.Delete (id);
    }
}

A ProductService az IProductRepository-t injektálja függőségként az építőjében, majd a Törlés módszerrel használja.

Helyes gyakorlatok:

  • Határozza meg a szükséges függőségeket a szolgáltatási konstruktorban. Így a szolgáltatás nem építhető fel függőségei nélkül.
  • Az injektált függőséget rendelje hozzá az írásvédett mezőhöz / tulajdonsághoz (annak megakadályozása érdekében, hogy a módszerben véletlenül további értéket rendeljen hozzá).

Injektálás

Az ASP.NET Core szokásos függőség-befecskendező tartálya nem támogatja a tulajdonság-befecskendezést. De használhat egy másik tartályt, amely támogatja az ingatlan-befecskendezést. Példa:

a Microsoft.Extensions.Logging használata;
a Microsoft.Extensions.Logging.Abstrations használata;
névtér MyApp
{
    nyilvános osztályú ProductService
    {
        nyilvános ILogger  naplózó {kap; készlet; }
        magán readonly IProductRepository _productRepository;
        nyilvános ProductService (IProductRepository productRepository)
        {
            _productRepository = productRepository;
            Naplózó = NullLogger  .Instance;
        }
        public void törlés (int id)
        {
            _productRepository.Delete (id);
            Logger.LogInformation (
                $ "Törölt egy terméket id = {id}");
        }
    }
}

A ProductService deklarálja a Logger tulajdonságot nyilvános beállítóval. A függőség-befecskendező tartály beállíthatja a naplózót, ha rendelkezésre áll (korábban regisztrált a DI-tartályba).

Helyes gyakorlatok:

  • Csak az opcionális függőségeknél használjon ingatlaninjekciót. Ez azt jelenti, hogy szolgáltatása megfelelő módon működhet ezeknek a függőségeknek a nélkül.
  • Használjon Null objektummintát (mint ahogy ebben a példában), ha lehetséges. Ellenkező esetben mindig ellenőrizze, hogy nincs-e semmi a függőség függvényében.

Szolgáltató kereső

A szolgáltatásmeghatározó minta egy másik módja a függőségek megszerzésének. Példa:

nyilvános osztályú ProductService
{
    magán readonly IProductRepository _productRepository;
    csak olvasható ILogger  _logger;
    nyilvános ProductService (IServiceProvider serviceProvider)
    {
        _productRepository = serviceProvider
          .GetRequiredService  ();
        _logger = serviceProvider
          .GetService > () ??
            NullLogger  .Instance;
    }
    public void törlés (int id)
    {
        _productRepository.Delete (id);
        _logger.LogInformation ($ "Törölte az id = {id} terméket");
    }
}

A ProductService az IServiceProvider-t fecskendezi és a függőségeket feloldja ezzel. A GetRequiredService kivételt tesz, ha a kért függőséget korábban nem regisztrálták. Másrészt a GetService ebben az esetben csak null értéket ad vissza.

Amikor megoldja a konstruktoron belüli szolgáltatásokat, akkor a szolgáltatás felszabadításakor azok felszabadulnak. Tehát nem érdekli a kivitelezőn belüli szolgáltatások felszabadítása / ártalmatlanítása (csakúgy, mint a kivitelező és az ingatlanbefecskendezés).

Helyes gyakorlatok:

  • Ahol csak lehetséges, ne használja a szolgáltatásmeghatározó mintát (ha a szolgáltatás típusa ismert a fejlesztési időben). Mert ez implicité teszi a függőségeket. Ez azt jelenti, hogy a szolgáltatás egy példányának létrehozásakor nem lehetséges a függőségek könnyű észlelése. Ez különösen fontos az egységteszteknél, ahol érdemes lehet gátolni a szolgáltatás egyes függőségeit.
  • Ha lehetséges, oldja meg a függőségeket a szolgáltató kivitelezőjén. A szolgáltatási módszerrel történő megoldás az alkalmazást bonyolultabbá és hibásabbá teszi. A következő szakaszokban tárgyalom a problémákat és a megoldásokat.

Service Life Times

Az ASP.NET központi függőségi befecskendezésének három élettartama van:

  1. Az átmeneti szolgáltatások minden alkalommal jönnek létre, amikor befecskendezik vagy igénylik.
  2. Hatályonként jönnek létre a szélesebb körű szolgáltatások. Egy webalkalmazásban minden webes kérés új, elválasztott szolgáltatási kört hoz létre. Ez azt jelenti, hogy a hatókörű szolgáltatásokat általában webes kérésenként hozzák létre.
  3. A Singleton szolgáltatásokat DI tartályonként hozzák létre. Ez általában azt jelenti, hogy alkalmazásonként csak egyszer készülnek el, majd az alkalmazás teljes élettartama alatt felhasználhatók.

A DI konténer nyomon követi az összes megoldott szolgáltatást. A szolgáltatásokat élettartamuk végén engedik el és bocsátják el:

  • Ha a szolgáltatásnak függősége van, akkor ezeket is automatikusan engedik el és ártalmatlanítják.
  • Ha a szolgáltatás megvalósítja az azonosítható felületet, akkor a Dispose módszert automatikusan felhívják a szolgáltatás kiadására.

Helyes gyakorlatok:

  • Regisztrálja szolgáltatásait átmenetileg, ahol csak lehetséges. Mivel az átmeneti szolgáltatások megtervezése egyszerű. Általában nem törődsz a többszálú és memóriaszivárgásokkal, és tudod, hogy a szolgáltatás élettartama rövid.
  • Használjon óvatos szolgáltatási élettartamot, mivel bonyolult lehet, ha gyermekszolgáltatási köreket hoz létre, vagy ezeket a szolgáltatásokat nem webes alkalmazásból használja.
  • Az egyedi élettartamot óvatosan használja, azóta foglalkoznia kell a többszálú kezeléssel és a lehetséges memóriaszivárgási problémákkal.
  • Ne függjen a szingulett szolgáltatás átmeneti vagy kiterjedt szolgáltatásától. Mivel az átmeneti szolgáltatás szingulett példánygá válik, amikor a szingulett szolgáltatás befecskendezi, és ez problémákat okozhat, ha az átmeneti szolgáltatást nem úgy tervezték, hogy támogassa egy ilyen forgatókönyvet. Az ASP.NET Core alapértelmezett DI tárolója ilyen esetekben már kivételeket hoz.

Szolgáltatások megoldása egy módszer-testben

Bizonyos esetekben előfordulhat, hogy a szolgáltatás egyik módszerével meg kell oldania egy másik szolgáltatást. Ilyen esetekben ügyeljen arra, hogy használat után engedje el a szolgáltatást. A legjobb módszer ennek biztosítására a szolgáltatási kör létrehozása. Példa:

nyilvános osztályú PriceCalculator
{
    csak olvasható IServiceProvider _serviceProvider;
    nyilvános árkalkulátor (IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    nyilvános úszó kiszámítása (Terméktermék, int szám,
      Írja be az taxStrategyServiceType)
    {
        használata (var skála = _serviceProvider.CreateScope ())
        {
            var taxStrategy = (ITaxStrategy) hatálya.ServiceProvider
              .GetRequiredService (taxStrategyServiceType);
            var ár = termék.Ár * szám;
            visszatérő ár + taxStrategy.CalculateTax (ár);
        }
    }
}

A PriceCalculator befecskendezi az IServiceProvider-t a konstruktorába, és hozzárendel egy mezőhöz. A PriceCalculator ezt követően a Számítás módszerrel használja fel a gyermekszolgáltatás hatókörének létrehozásához. A Szolgáltatások feloldása céljából a activ.ServiceProvider szolgáltatást használja az injektált _serviceProvider példány helyett. Így az összes hatókörből feloldott szolgáltatás automatikusan felszabadul / diszpozícióba kerül a felhasználási utasítás végén.

Helyes gyakorlatok:

  • Ha egy szolgáltatást egy módszertanban old meg, mindig hozzon létre egy gyermekszolgáltatási hatókört annak biztosítása érdekében, hogy a feloldott szolgáltatások megfelelően kerüljenek kiadásra.
  • Ha egy módszer argumentumként az IServiceProvider szolgáltatást kapja, akkor közvetlenül kiszámíthatja a szolgáltatásokat tőle, anélkül hogy törődne a kiadással / ártalmatlanítással. A szolgáltatási kör létrehozása / kezelése a módszerét felhívó kód felelőssége. Ezen elv követése tisztábbá teszi a kódot.
  • Ne tartson utalást egy megoldott szolgáltatásra! Ellenkező esetben memóriaszivárgást okozhat, és akkor hozzáférhet egy megsemmisített szolgáltatáshoz, amikor később használja az objektumhivatkozást (kivéve, ha a megoldott szolgáltatás egyedüli).

Singleton szolgáltatások

A Singleton szolgáltatásokat általában az alkalmazás állapotának megőrzésére tervezték. A gyorsítótár jó példa az alkalmazás állapotaira. Példa:

nyilvános osztályú FileService
{
    privát, csak olvasható egyidejű szótár  _cache;
    nyilvános FileService ()
    {
        _cache = új egyidejű szótár  ();
    }
    nyilvános byte [] GetFileContent (stringPathPath)
    {
        return _cache.GetOrAdd (filePath, _ =>
        {
            vissza a File.ReadAllBytes (filePath);
        });
    }
}

A FileService egyszerűen gyorsítótárazza a fájl tartalmát a lemezolvasás csökkentése érdekében. Ezt a szolgáltatást szingulettként kell regisztrálni. Ellenkező esetben a gyorsítótárazás nem fog működni a várt módon.

Helyes gyakorlatok:

  • Ha a szolgáltatás állapotot tart, akkor hozzá kell férnie ehhez az állapothoz szál-biztonságos módon. Mivel az összes kérés egyidejűleg ugyanazt a szolgáltatási példányt használja. A szálbiztonság érdekében a ConcurrentD Dictionary-t használtam a szótár helyett.
  • Ne használja a szingulett szolgáltatásokból származó, átfogó vagy átmeneti szolgáltatásokat. Mivel az átmeneti szolgáltatásokat nem úgy tervezték meg, hogy azok biztonságosak legyenek. Ha ezeket használnia kell, akkor vigyázzon a többszálú menetre, miközben ezeket a szolgáltatásokat használja (például használjon zárat).
  • A memóriaszivárgásokat általában a szingulett szolgáltatások okozzák. Az alkalmazás végéig nem engedik szabadon / ártalmatlanítják. Tehát, ha megbeszélik osztályokat (vagy befecskendezik), de nem engedik fel / ártalmatlanítják őket, akkor is a memóriában maradnak az alkalmazás végéig. Ügyeljen arra, hogy megfelelő időben engedje el / dobja el őket. Lásd a Feloldási szolgáltatások a Test Test szakaszban.
  • Adatok gyorsítótárazása (a példában a fájl tartalma), létre kell hoznia egy mechanizmust a gyorsítótárazott adatok frissítésére / érvénytelenítésére, amikor az eredeti adatforrás megváltozik (amikor a példa esetében a gyorsítótárazott fájl megváltozik a lemezen).

Hatályos szolgáltatások

A teljes élettartam először jó jelöltnek tűnik arra, hogy web-kérési adatokat tároljon. Mivel az ASP.NET Core szolgáltatási kört hoz létre webes kérésenként. Tehát, ha egy szolgáltatást kiterjedőként regisztrál, akkor megoszthatja azt egy webes kérés során. Példa:

public class RequestItemsService
{
    privát, csak olvasható szótár  _items;
    public RequestItemsService ()
    {
        _items = új szótár  ();
    }
    public void Set (karakterlánc neve, objektumérték)
    {
        _items [név] = érték;
    }
    nyilvános objektum Get (karakterlánc neve)
    {
        return _items [név];
    }
}

Ha regisztrálja a RequestItemsService alkalmazási körét, és két különféle szolgáltatásba injektálja, akkor kaphat egy elemet, amelyet hozzáadnak egy másik szolgáltatáshoz, mert ugyanazon RequestItemsService példányt fogják megosztani. Ez az, amit elvárunk a hatályos szolgáltatásoktól.

De .. lehet, hogy a tény nem mindig ilyen. Ha létrehoz egy gyermekszolgáltatási kört, és a gyermekkörtől oldja meg a RequestItemsService szolgáltatást, akkor megkapja a RequestItemsService új példányát, és nem fog működni a várt módon. Tehát a hatókörű szolgáltatás nem mindig jelenti webes kérésenként példányt.

Gondolhat úgy, hogy nem követett el ilyen nyilvánvaló hibát (a gyermek hatókörén belüli hatókör feloldása). De ez nem hiba (nagyon rendszeres használat), és az eset nem olyan egyszerű. Ha nagy a függőségi gráf a szolgáltatásaid között, akkor nem tudhatja, valakit létrehoztak-e gyermekek körét, és megoldották-e egy olyan szolgáltatást, amely újabb szolgáltatást nyújt ... amely végül kiterjed egy kiterjedt szolgáltatásra.

Jó gyakorlatok:

  • A hatókörű szolgáltatás optimalizálásnak tekinthető, ha túl sok szolgáltatás ad hozzá egy webes kéréshez. Így ezek a szolgáltatások ugyanazon webes kérés során a szolgáltatás egyetlen példányát fogják használni.
  • A szélesebb körű szolgáltatásokat nem kell szálbiztonságként megtervezni. Mivel ezeket általában egyetlen web-kérelemnek / szálnak kell használni. De… ebben az esetben nem szabad megosztania a szolgáltatási köröket a különböző szálak között!
  • Legyen óvatos, ha egy hatókörű szolgáltatást tervez, hogy az adatokat más szolgáltatások között megoszthassa webes kérésben (fentebb kifejtve). Internetes kérésenként adatokat tárolhat a HttpContext-ben (az IHttpContextAccessor-t hozzá kell férnie ahhoz, hogy elérje), ez a legbiztonságosabb módszer. A HttpContext élettartama nem terjed ki. Valójában egyáltalán nem regisztrálták a DI-ben (ezért nem adja be, hanem az IHttpContextAccessor-t). A HttpContextAccessor megvalósítása az AsyncLocal eszközt használja ugyanazon HttpContext megosztására egy webes kérés során.

Következtetés

A függőségi injekció eleinte egyszerűnek tűnik, de lehetséges, hogy többszálú és memóriaszivárgási problémák merülnek fel, ha nem tartja be a szigorú alapelveket. Az ASP.NET Boilerplate keret fejlesztése során saját tapasztalataim alapján megosztottam néhány jó alapelvet.