ASP.NET asosiy qaramlik in'ektsiyasi bo'yicha eng yaxshi amaliyotlar, maslahatlar va tavsiyalar

Ushbu maqolada men ASP.NET Core ilovalarida "Dependency Injection" dan foydalanish bo'yicha o'z tajribam va takliflarimni baham ko'raman. Ushbu printsiplarning asosi quyidagilardir;

  • Xizmatlarni va ularning qaramligini samarali loyihalash.
  • Ko'p tishli muammolarni oldini olish.
  • Xotiraning buzilishini oldini olish.
  • Mumkin bo'lgan xatolarning oldini olish.

Ushbu maqola siz allaqachon "Dependency Injection" va "ASP.NET Core" bilan yaxshi tanish ekanligingizni taxmin qilmoqda. Agar yo'q bo'lsa, avval ASP.NET Core Dependency Injection hujjatini o'qing.

Asoslar

Konstruktor in'ektsiyasi

Konstruktor in'ektsiyasi xizmatning qurilishiga bog'liqlikni e'lon qilish va olish uchun ishlatiladi. Masalan:

ommaviy sinf ProductService
{
    xususiy o'qish uchun faqat IProductRepository _productRepository;
    public ProductService (IP-mahsulotRepozitoriy mahsulotiRepozitariya)
    {
        _productRepository = productRepository;
    }
    jamoat bo'shlig'ini o'chirish (int id)
    {
        _productRepository.Delete (id);
    }
}

ProductService IProductRepository-ni o'z konstruktoriga bog'liqlik sifatida kiritadi va keyin uni Delete usulida ishlatadi.

Yaxshi amaliyotlar:

  • Xizmat konstruktorida zarur bo'lgan bog'liqlikni aniqlang. Shunday qilib, xizmat uning qaramliksiz qurilishi mumkin emas.
  • In'ektsion bog'liqlikni faqat o'qish uchun mo'ljallangan maydon / mulkka tayinlang (usulga tasodifan boshqa qiymat berishining oldini olish uchun).

Mulkni in'ektsiya qilish

ASP.NET Core'ning standart qaramlik in'ektsiyasi konteyneri mulk in'ektsiyasini qo'llab-quvvatlamaydi. Lekin siz mulk in'ektsiyasini qo'llab-quvvatlaydigan boshqa idishni ishlatishingiz mumkin. Masalan:

Microsoft.Extensions.Logging-dan foydalanish;
Microsoft.Extensions.Logging.Abstraction-dan foydalanish;
MyApp nomli bo'sh joy
{
    ommaviy sinf ProductService
    {
        public ILogger  Logger {get; to'siq; }
        xususiy o'qish uchun faqat IProductRepository _productRepository;
        public ProductService (IP-mahsulotRepozitoriy mahsulotiRepozitariya)
        {
            _productRepository = productRepository;
            Logger = NullLogger  .stansiya;
        }
        jamoat bo'shlig'ini o'chirish (int id)
        {
            _productRepository.Delete (id);
            Logger.LogInformation (
                $ "ID = {id} bilan mahsulot o'chirildi");
        }
    }
}

ProductService Logger xususiyatini ommaviy setter bilan e'lon qiladi. Bog'lanish in'ektsiyasi konteyneri mavjud bo'lsa, jurnalni o'rnatishi mumkin (oldin DI konteynerida ro'yxatdan o'tgan).

Yaxshi amaliyotlar:

  • Mulkni in'ektsiyadan faqat majburiy qaramlik uchun foydalaning. Bu sizning xizmatingiz ushbu bog'liqliklarsiz to'g'ri ishlashi mumkinligini anglatadi.
  • Iloji bo'lsa, Null Object Pattern modelidan foydalaning (ushbu misolda bo'lgani kabi). Aks holda, qaramlikni ishlatganda har doim nol mavjudligini tekshiring.

Xizmatni aniqlash joyi

Xizmatni aniqlashning o'ziga xos xususiyati - qaramlikni olishning yana bir usuli. Masalan:

ommaviy sinf ProductService
{
    xususiy o'qish uchun faqat IProductRepository _productRepository;
    xususiy o'qish uchun faqat ILogger  _logger;
    public ProductService (IServiceProvider serviceProvider)
    {
        _productRepository = serviceProvider
          .GetRequiredService  ();
        _logger = serviceProvider
          .GetService > () ??
            NullLogger  .Mustaqillik;
    }
    jamoat bo'shlig'ini o'chirish (int id)
    {
        _productRepository.Delete (id);
        _logger.LogInformation ($ "id = {id} bo'lgan mahsulot o'chirildi");
    }
}

ProductService IServiceProvider-ni in'ektsiya qiladi va undan foydalangan holda qaramlikni hal qiladi. Agar so'ralgan qaramlik ilgari ro'yxatdan o'tmagan bo'lsa, GetRequiredService istisno qiladi. Boshqa tomondan, GetService shunchaki nullni qaytaradi.

Siz konstruktor ichidagi xizmatlarni hal qilsangiz, ular xizmat tugashi bilanoq ular chiqariladi. Shunday qilib, siz konstruktor ichidagi (qurilishchi va mulkni kiritish kabi) xizmatlarni bo'shatish / tasarruf etish haqida qayg'urmaysiz.

Yaxshi amaliyotlar:

  • Iloji boricha xizmatni sozlash moslamasidan foydalanmang (agar xizmat turi ishlab chiqish vaqtida ma'lum bo'lsa). Chunki bu qaramlikni yashirin qiladi. Demak, xizmat namunasini yaratishda bog'liqlikni osongina ko'rish mumkin emas. Bu, ayniqsa, xizmatning ba'zi bog'liqliklarini masxara qilishni xohlashingiz mumkin bo'lgan birlik sinovlari uchun juda muhimdir.
  • Iloji bo'lsa, xizmat konstruktoridagi bog'liqlikni hal qiling. Xizmat usulida hal qilish sizning ilovangizni murakkablashtiradi va xatolarga moyil bo'ladi. Men keyingi qismlarda muammolar va echimlarni yoritaman.

Xizmat muddati

ASP.NET asosiy qaramlik in'ektsiyasida uchta xizmat muddati mavjud:

  1. Vaqtinchalik xizmatlar har safar AOK qilingan yoki so'ralganda yaratiladi.
  2. Xizmat doirasi har bir sohada yaratiladi. Veb-ilovada har bir veb-so'rov yangi ajratilgan xizmat doirasini yaratadi. Bu shuni anglatadiki, cheklangan xizmatlar odatda har bir veb-so'rov uchun yaratiladi.
  3. Singleton xizmatlari har bir DI konteyner uchun yaratiladi. Umuman olganda, ular bitta dastur uchun atigi bir marta yaratilgan va undan keyin butun dastur muddati uchun ishlatilgan degan ma'noni anglatadi.

DI konteyneri barcha hal qilingan xizmatlarni kuzatib boradi. Xizmatlar muddati tugashi bilan ozod qilinadi va yo'q qilinadi:

  • Agar xizmatda bog'liqliklar mavjud bo'lsa, ular avtomatik ravishda chiqariladi va yo'q qilinadi.
  • Agar xizmat IDisposable interfeysini amalga oshirsa, "Dispose" usuli avtomatik ravishda xizmatni chaqirishga chaqiriladi.

Yaxshi amaliyotlar:

  • Imkoniyat boricha xizmatlaringizni vaqtincha ro'yxatdan o'tkazing. Chunki vaqtinchalik xizmatlarni loyihalash juda oddiy. Siz odatda ko'p tarmoqli va xotira etishmasligi haqida qayg'urmaysiz va xizmat qisqa umr ko'rishini bilasiz.
  • Cheklangan xizmatning butun umridan ehtiyotkorlik bilan foydalaning, chunki agar siz bolalarga xizmat ko'rsatish doirasini yaratsangiz yoki ushbu xizmatlarni veb-ilovadan foydalanmasangiz, bu juda qiyin bo'lishi mumkin.
  • Singleton-ning umr bo'yi ehtiyotkorlik bilan ishlating, shundan keyin siz ko'p tishli va xotira etishmasligi bilan bog'liq muammolarni hal qilishingiz kerak.
  • Singleton xizmatidan vaqtinchalik yoki cheklangan xizmatga bog'liq bo'lmang. Chunki vaqtinchalik xizmat singlton xizmati singlga kirganda va u vaqtinchalik xizmat bunday stsenariyni qo'llab-quvvatlashga mo'ljallanmagan bo'lsa, muammolarga olib kelishi mumkin. Bunday holatlarda ASP.NET Core standart DI konteyneri bundan mustasno.

Xizmatlarni usul tanasida hal qilish

Ba'zi hollarda, sizning xizmatingiz usulida boshqa xizmatni hal qilishingiz kerak bo'lishi mumkin. Bunday hollarda, xizmatdan foydalangandan keyin ozod qilinishingizga ishonch hosil qiling. Buni ta'minlashning eng yaxshi usuli bu xizmat doirasini yaratishdir. Masalan:

ommaviy sinf PriceCalculator
{
    faqat o'qish uchun IServiceProvider _serviceProvider;
    jamoat narxlari kalkulyatori (IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    jamoat narxini hisoblash (mahsulot mahsuloti, son soni,
      SoliqStrategyServiceType turini)
    {
        foydalanish (var range = _serviceProvider.CreateScope ())
        {
            var taxStrategy = (ITaxStrategy) qamrovi.ServiceProvider
              .GetRequiredService (taxStrategyServiceType);
            var price = mahsulot .Qiymat * soni;
            qaytish narxi + taxStrategy.CalculateTax (narx);
        }
    }
}

PriceCalculator IServiceProvider-ni o'z konstruktoriga kiritadi va uni maydonga tayinlaydi. PriceCalculator undan keyin bolalar uchun xizmat doirasini yaratish uchun uni "Hisoblash" usuli ichida ishlatadi. U in'ektsiya qilingan _serviceProvider in'ektsiyasining o'rniga xizmatlarni hal qilish uchun range.ServiceProvider-dan foydalanadi. Shunday qilib, foydalanish doirasidan chiqadigan barcha xizmatlar foydalanish bayonnomasi oxirida avtomatik ravishda chiqariladi / chiqariladi.

Yaxshi amaliyotlar:

  • Agar siz biron bir xizmatni usul idorasida hal qilsangiz, hal qilingan xizmatlarning to'g'ri chiqarilishini ta'minlash uchun har doim bolalar uchun xizmat doirasini yarating.
  • Agar usul IServiceProvider-ni argument sifatida qabul qilsa, unda siz xizmatlarni to'g'ridan-to'g'ri hal qilishingiz mumkin, ularni bo'shatish / tarqatib yubormang. Xizmat doirasini yaratish / boshqarish sizning usulingizni chaqiradigan kodning javobgarligi. Ushbu tamoyilga amal qilish sizning kodingizni tozalaydi.
  • Qaror qilingan xizmatga murojaat qilmang! Aks holda, bu xotira etishmasligiga olib kelishi mumkin va siz ob'ekt ma'lumotnomasini keyinchalik ishlatganingizda yo'q qilingan xizmatga kirishingiz mumkin (agar hal qilingan xizmat singlton bo'lmasa).

Singleton xizmatlari

Singleton xizmatlari odatda dastur holatini saqlab qolish uchun mo'ljallangan. Kesh dastur holatlarining yaxshi namunasidir. Masalan:

ommaviy sinf FileService
{
    xususiy o'qish uchun ConcurrentDordam  _cache;
    public FileService ()
    {
        _cache = yangi ConcurrentDordam  ();
    }
    umumiy bayt [] GetFileContent (string faylPath)
    {
        return _cache.GetOrAdd (filePath, _ =>)
        {
            File.ReadAllBytes-ni qaytaring (filePath);
        });
    }
}

FileService shunchaki disk o'qilishini qisqartirish uchun fayl tarkibini keshlaydi. Ushbu xizmat singlton sifatida ro'yxatdan o'tkazilishi kerak. Aks holda, keshlash kutilganidek ishlamaydi.

Yaxshi amaliyotlar:

  • Agar xizmat shtatga ega bo'lsa, u ushbu holatga erkin kirish huquqiga ega bo'lishi kerak. Chunki barcha so'rovlar bir xil xizmatdan foydalanadi. Men ipning xavfsizligini ta'minlash uchun lug'at o'rniga ConcurrentDordam dan foydalanganman.
  • Singleton xizmatlarining kengaytirilgan yoki vaqtinchalik xizmatlaridan foydalanmang. Sababi, vaqtinchalik xizmatlar ulanish xavfsiz bo'lishi uchun ishlab chiqilmasligi mumkin. Agar siz ulardan foydalanishingiz kerak bo'lsa, ushbu xizmatlardan foydalanganda ko'p tarmoqli bo'lishga ahamiyat bering (masalan, qulflash funktsiyasidan foydalaning).
  • Xotiraning etishmasligi, odatda, singleton xizmatlaridan kelib chiqadi. Ular ariza tugaguniga qadar ozod qilinmaydi / tasarruf etilmaydi. Shunday qilib, agar ular darslarni tezkor o'tkazsalar (yoki in'ektsiya qilsalar), lekin ularni bo'shatib yubormasalar va yo'q qilmasa, ular dastur oxirigacha xotirada qoladi. Ularni o'z vaqtida ozod qilganingizga va tasarruf qilganingizga ishonch hosil qiling. Yuqoridagi usul bo'yicha xizmatlarni hal qilish bo'limiga qarang.
  • Agar siz ma'lumotni keshlasangiz (ushbu misoldagi fayl tarkiblari), asl ma'lumot manbai o'zgarganda keshlangan ma'lumotni yangilash / bekor qilish mexanizmini yaratishingiz kerak (bu misol uchun diskdagi keshlangan fayl o'zgarganda).

Xizmat doirasi

Avval umr bo'yi veb-so'rov ma'lumotlarini saqlash uchun yaxshi nomzod ko'rinadi. Chunki ASP.NET Core veb-so'rov uchun xizmat doirasini yaratadi. Shunday qilib, agar siz xizmatni keng miqyosda ro'yxatdan o'tkazsangiz, uni veb-so'rov paytida almashish mumkin. Masalan:

ommaviy sinf RequestItemsService
{
    xususiy o'qish uchun faqat lug'at  _items;
    ommaviy RequestItemsService ()
    {
        _items = yangi lug'at  ();
    }
    public void Set (satr nomi, ob'ekt qiymati)
    {
        _items [name] = qiymat;
    }
    public ob'ekt Get (satr nomi)
    {
        return _items [name];
    }
}

Agar siz RequestItemsService-ni chegaralangan deb ro'yxatdan o'tkazsangiz va uni ikki xil xizmatga kiritsangiz, unda siz boshqa xizmatdan qo'shilgan narsani olishingiz mumkin, chunki ular bir xil RequestItemsService nusxasini bo'lishadilar. Bu biz keng qamrovli xizmatlardan kutayotgan narsadir.

Ammo .. haqiqat har doim ham shunday bo'lmasligi mumkin. Agar siz bolalarga xizmat ko'rsatish doirasini yaratsangiz va RequestItemsService-ni bolalar doirasidan chiqarib yuborsangiz, siz RequestItemsService-ning yangi namunasini olasiz va u siz kutganingizdek ishlamaydi. Shunday qilib, qamrovli xizmat har doim ham bir veb-so'rov uchun nusxani anglatmaydi.

Siz bunday aniq xato qilmaysiz deb o'ylashingiz mumkin (bolalar doirasidagi doirani hal qilish). Ammo, bu xato emas (juda oddiy foydalanish) va ish bunday oddiy bo'lmasligi mumkin. Agar sizning xizmatlaringiz o'rtasida katta bog'liqlik grafigi mavjud bo'lsa, siz hech kim bolalar doirasini yaratganmi yoki boshqa xizmatni in'ektsiya qiladigan xizmatni hal qilgan-qilmaganligini bilolmaysiz ... oxir-oqibat u xizmat doirasini kiritadi.

Yaxshi amaliyot:

  • Cheklangan xizmat veb-so'rovda juda ko'p xizmatlar tomonidan kiritilganda optimallashtirish deb o'ylash mumkin. Shunday qilib, ushbu xizmatlarning barchasi bitta veb-so'rov paytida bitta xizmatdan foydalanadi.
  • Cheklangan xizmatlarni xavfsiz tarzda ishlab chiqishga hojat yo'q. Chunki, ular odatda bitta veb-so'rov / mavzu tomonidan ishlatilishi kerak. Ammo ... bu holda, siz xizmat doirasini turli xil mavzular o'rtasida taqsimlamasligingiz kerak!
  • Agar veb-so'rovda boshqa xizmatlar o'rtasida ma'lumotlarni almashish uchun kengaytirilgan xizmatni ishlab chiqsangiz ehtiyot bo'ling (yuqorida tavsiflangan). Siz har bir veb-so'rov ma'lumotlarini HttpContext-da saqlashingiz mumkin (kirish uchun IHttpContextAccessorni kiriting), bu eng xavfsiz usul. HttpContextning amal qilish muddati cheklanmagan. Aslida, u DI-da umuman ro'yxatga olinmagan (shuning uchun siz uni in'ektsiya qilmaysiz, balki uning o'rniga IHttpContextAccessor kiritasiz). HttpContextAccessor-ni amalga oshirish veb-so'rov paytida bir xil HttpContextni almashish uchun AsyncLocal-dan foydalanadi.

Xulosa

Dastlab qaramlik in'ektsiya qilish oson ko'rinadi, ammo agar siz ba'zi bir jiddiy printsiplarga rioya qilmasangiz, ko'p tarmoqli va xotira etishmasligi bilan bog'liq muammolar mavjud. Men ASP.NET qaynoq doirasini ishlab chiqishda o'z tajribamga asoslangan ba'zi yaxshi printsiplar bilan o'rtoqlashdim.