ASP.NET कोर निर्भरता इंजेक्शन सबसे अच्छा अभ्यास, टिप्स और ट्रिक्स

इस लेख में, मैं ASP.NET कोर अनुप्रयोगों में निर्भरता इंजेक्शन का उपयोग करने पर अपने अनुभव और सुझाव साझा करूंगा। इन सिद्धांतों के पीछे प्रेरणा हैं;

  • प्रभावी ढंग से सेवाओं और उनकी निर्भरता को डिजाइन करना।
  • बहु सूत्रण मुद्दों को रोकना।
  • स्मृति-लीक को रोकना।
  • संभावित बगों को रोकना।

यह आलेख मानता है कि आप पहले से ही बुनियादी स्तर पर निर्भरता इंजेक्शन और ASP.NET कोर से परिचित हैं। यदि नहीं, तो कृपया पहले ASP.NET कोर डिपेंडेंसी इंजेक्शन प्रलेखन पढ़ें।

मूल बातें

कंस्ट्रक्टर इंजेक्शन

कंस्ट्रक्टर इंजेक्शन का उपयोग सेवा निर्माण पर किसी सेवा की निर्भरता को घोषित करने और प्राप्त करने के लिए किया जाता है। उदाहरण:

सार्वजनिक वर्ग ProductService
{
    निजी आसानी से IProductRepository _productRepository;
    सार्वजनिक उत्पाद सेवा (IProductRepository उत्पाद
    {
        _productRepository = productRepository;
    }
    सार्वजनिक शून्य हटाएं (int id)
    {
        _productRepository.Delete (आईडी);
    }
}

ProductService IProductRepository को उसके कंस्ट्रक्टर में एक निर्भरता के रूप में इंजेक्ट कर रही है और फिर इसे Delete विधि के अंदर उपयोग कर रही है।

अच्छे आचरण:

  • सेवा निर्माणकर्ता में स्पष्ट रूप से आवश्यक निर्भरताएँ निर्धारित करें। इस प्रकार, सेवा का निर्माण उसकी निर्भरता के बिना नहीं किया जा सकता है।
  • एक रीड ओनली फील्ड / प्रॉपर्टी पर इंजेक्शन की निर्भरता असाइन करें (गलती से किसी अन्य मान को एक विधि के अंदर असाइन करने से रोकने के लिए)।

संपत्ति इंजेक्शन

ASP.NET Core के मानक निर्भरता इंजेक्शन कंटेनर प्रॉपर्टी इंजेक्शन का समर्थन नहीं करता है। लेकिन आप प्रॉपर्टी इंजेक्शन का समर्थन करने वाले दूसरे कंटेनर का उपयोग कर सकते हैं। उदाहरण:

Microsoft.Extensions.Logging का उपयोग कर;
Microsoft.Extensions.ogging.Abstractions का उपयोग कर;
नामस्थान MyApp
{
    सार्वजनिक वर्ग ProductService
    {
        सार्वजनिक ILogger  लकड़हारा {प्राप्त करें; सेट; }
        निजी आसानी से IProductRepository _productRepository;
        सार्वजनिक उत्पाद सेवा (IProductRepository उत्पाद
        {
            _productRepository = productRepository;
            लकड़हारा = NullLogger  .Instance;
        }
        सार्वजनिक शून्य हटाएं (int id)
        {
            _productRepository.Delete (आईडी);
            Logger.LogInformation (
                $ "आईडी = {आईडी}" के साथ एक उत्पाद हटा दिया गया);
        }
    }
}

ProductService सार्वजनिक सेटर के साथ एक लकड़हारा संपत्ति घोषित कर रहा है। निर्भरता इंजेक्शन कंटेनर लकड़हारा सेट कर सकता है अगर यह उपलब्ध है (डीआई कंटेनर से पहले पंजीकृत)।

अच्छे आचरण:

  • केवल वैकल्पिक निर्भरता के लिए संपत्ति इंजेक्शन का उपयोग करें। इसका मतलब है कि आपकी सेवा प्रदान की गई इन निर्भरताओं के बिना ठीक से काम कर सकती है।
  • यदि संभव हो तो नल ऑब्जेक्ट पैटर्न (जैसे इस उदाहरण में) का उपयोग करें। अन्यथा, निर्भरता का उपयोग करते समय हमेशा शून्य की जांच करें।

सेवा लोकेटर

सेवा लोकेटर पैटर्न निर्भरता प्राप्त करने का एक और तरीका है। उदाहरण:

सार्वजनिक वर्ग ProductService
{
    निजी आसानी से IProductRepository _productRepository;
    निजी आसानी से ILogger  _logger;
    सार्वजनिक उत्पाद सेवा (IServiceProvider सेवाप्रोवाइडर)
    {
        _प्रोडक्ट रिपोसिटरी = सर्विसप्रोइडर
          .GetRequiredService  ();
        _logger = serviceProvider
          .GetService > () ??
            NullLogger  .Instance;
    }
    सार्वजनिक शून्य हटाएं (int id)
    {
        _productRepository.Delete (आईडी);
        _logger.LogInformation ($ "id = {id}" के साथ उत्पाद हटा दिया गया);
    }
}

ProductService IServiceProvider को इंजेक्ट कर रहा है और इसका उपयोग करके निर्भरता को हल कर रहा है। यदि अनुरोधित निर्भरता पहले पंजीकृत नहीं थी, तो GetRequiredService अपवाद फेंकता है। दूसरी ओर, GetService सिर्फ उस मामले में शून्य देता है।

जब आप कंस्ट्रक्टर के अंदर सेवाओं का समाधान करते हैं, तो सेवा जारी होने पर उन्हें छोड़ दिया जाता है। इसलिए, आप कंस्ट्रक्टर (बस कंस्ट्रक्टर और प्रॉपर्टी इंजेक्शन की तरह) के अंदर हल की गई सेवाओं को जारी करने / निपटाने के बारे में परवाह नहीं करते हैं।

अच्छे आचरण:

  • जहां भी संभव हो (यदि सेवा का प्रकार विकास समय में जाना जाता है) सेवा लोकेटर पैटर्न का उपयोग न करें। क्योंकि यह निर्भरताओं को अंतर्निहित करता है। इसका मतलब यह है कि सेवा का एक उदाहरण बनाते समय निर्भरता को आसानी से देखना संभव नहीं है। यह यूनिट परीक्षणों के लिए विशेष रूप से महत्वपूर्ण है जहां आप किसी सेवा की कुछ निर्भरता का मजाक उड़ाना चाहते हैं।
  • यदि संभव हो तो सेवा निर्माता में निर्भरता को हल करें। एक सेवा पद्धति में हल करने से आपका आवेदन अधिक जटिल और त्रुटि प्रवण हो जाता है। मैं अगले खंडों में समस्याओं और समाधानों को कवर करूंगा।

सेवा जीवन टाइम्स

ASP.NET कोर निर्भरता इंजेक्शन में तीन सेवा जीवनकाल हैं:

  1. हर बार इंजेक्शन या अनुरोध किए जाने पर क्षणिक सेवाएं बनाई जाती हैं।
  2. स्कोप वाली सेवाओं को प्रति स्कोप बनाया जाता है। एक वेब अनुप्रयोग में, हर वेब अनुरोध एक नया अलग सेवा क्षेत्र बनाता है। इसका मतलब है कि स्कोप की गई सेवाएं आम तौर पर प्रति वेब अनुरोध के अनुसार बनाई जाती हैं।
  3. सिंग्लटन सेवाएं डीआई कंटेनर के अनुसार बनाई जाती हैं। आम तौर पर इसका मतलब है कि वे प्रति आवेदन केवल एक समय बनाया जाता है और फिर पूरे जीवन काल के लिए उपयोग किया जाता है।

डीआई कंटेनर सभी हल सेवाओं का ट्रैक रखता है। जब उनका जीवनकाल समाप्त हो जाता है तो सेवा जारी और निस्तारण कर दी जाती है:

  • यदि सेवा में निर्भरताएं हैं, तो वे स्वचालित रूप से जारी और निपटाए जाते हैं।
  • यदि सेवा IDisposable इंटरफ़ेस को लागू करती है, तो डिस्पोज़ विधि स्वचालित रूप से सेवा रिलीज़ पर कॉल की जाती है।

अच्छे आचरण:

  • जहाँ भी संभव हो अपनी सेवाओं को क्षणिक के रूप में पंजीकृत करें। क्योंकि यह क्षणिक सेवाओं को डिजाइन करने के लिए सरल है। आप आमतौर पर मल्टी-थ्रेडिंग और मेमोरी लीक के बारे में परवाह नहीं करते हैं और आप जानते हैं कि सेवा का जीवन छोटा है।
  • यदि आप चाइल्ड सर्विस स्कोप बनाते हैं या गैर-वेब एप्लिकेशन से इन सेवाओं का उपयोग करते हैं, तो यह सावधानी से स्कोपेड सर्विस लाइफटाइम का उपयोग करें।
  • सिंगलटन लाइफटाइम का सावधानी से उपयोग करें तब से आपको मल्टी-थ्रेडिंग और संभावित मेमोरी लीक समस्याओं से निपटने की आवश्यकता है।
  • एक सिंगलटन सेवा से क्षणिक या स्कूप की गई सेवा पर निर्भर न रहें। क्योंकि क्षणिक सेवा एक एकल उदाहरण बन जाती है जब एक एकल सेवा इसे इंजेक्ट करती है और इससे समस्याएँ पैदा हो सकती हैं यदि क्षणिक सेवा को इस तरह के परिदृश्य का समर्थन करने के लिए डिज़ाइन नहीं किया गया है। ASP.NET Core का डिफ़ॉल्ट DI कंटेनर पहले से ही ऐसे मामलों में अपवाद फेंकता है।

एक विधि निकाय में सेवाएँ हल करना

कुछ मामलों में, आपको अपनी सेवा के तरीके में एक और सेवा को हल करने की आवश्यकता हो सकती है। ऐसे मामलों में, सुनिश्चित करें कि आप सेवा को उपयोग के बाद जारी करते हैं। यह सुनिश्चित करने का सबसे अच्छा तरीका है कि सेवा क्षेत्र बनाना है। उदाहरण:

सार्वजनिक वर्ग PriceCalculator
{
    निजी आसानी से IServiceProvider _serviceProvider;
    सार्वजनिक PriceCalculator (IServiceProvider सेवाप्रोवाइडर)
    {
        _serviceProvider = serviceProvider;
    }
    सार्वजनिक फ्लोट गणना (उत्पाद उत्पाद, इंट काउंट,
      टाइप करें टाइपस्ट्रेसी सर्विस टाइप)
    {
        उपयोग (var गुंजाइश = _serviceProvider.CreateScope ())
        {
            var taxStrategy = (ITaxStrategy) स्कोप। ServiceProvider
              .GetRequiredService (taxStrategyServiceType);
            var price = product.Price * गिनती;
            वापसी की कीमत + taxStrategy.CalculateTax (मूल्य);
        }
    }
}

PriceCalculator अपने कंस्ट्रक्टर में IServiceProvider को इंजेक्ट करता है और इसे एक फील्ड को असाइन करता है। PriceCalculator तब बाल सेवा क्षेत्र बनाने के लिए गणना पद्धति के अंदर इसका उपयोग करता है। यह इंजेक्ट _serviceProvider उदाहरण के बजाय, सेवाओं को हल करने के लिए स्कोप.सर्वप्रोवाइडर का उपयोग करता है। इस प्रकार, दायरे से हल की गई सभी सेवाएं स्वचालित रूप से जारी बयान के अंत में जारी / निपटा दी जाती हैं।

अच्छे आचरण:

  • यदि आप एक विधि निकाय में किसी सेवा का समाधान कर रहे हैं, तो हमेशा यह सुनिश्चित करने के लिए एक चाइल्ड सर्विस स्कोप बनाएं कि हल की गई सेवाओं को ठीक से जारी किया जाए।
  • यदि किसी विधि को एक तर्क के रूप में IServiceProvider मिलता है, तो आप बिना जारी किए / निपटान के बारे में परवाह किए बिना सीधे सेवाओं को इससे हल कर सकते हैं। सेवा क्षेत्र बनाना / प्रबंधित करना आपके कोड को कॉल करने की एक जिम्मेदारी है। इस सिद्धांत का पालन करने से आपका कोड साफ हो जाता है।
  • एक सुलझी हुई सेवा का संदर्भ न रखें! अन्यथा, यह मेमोरी लीक का कारण हो सकता है और आप बाद में ऑब्जेक्ट संदर्भ का उपयोग करते समय एक निपटारा सेवा तक पहुंचेंगे (जब तक कि सुलझी हुई सेवा एकल न हो)।

सिंगलटन सर्विसेज

सिंगलटन सेवाओं को आमतौर पर एक एप्लिकेशन स्थिति रखने के लिए डिज़ाइन किया जाता है। एक कैश एप्लिकेशन राज्यों का एक अच्छा उदाहरण है। उदाहरण:

सार्वजनिक वर्ग FileService
{
    निजी आसानी से समवर्ती ढाल  _cache;
    सार्वजनिक फ़ाइल सेवा ()
    {
        _cache = नया समवर्ती  बाइट []> ();
    }
    सार्वजनिक बाइट [] GetFileContent (स्ट्रिंग फ़ाइलपट)
    {
        वापसी _cache.GetOrAdd (फ़ाइलपथ, _ =>
        {
            File.ReadAllBytes (filePath) वापस करें;
        });
    }
}

FileService बस डिस्क को कम करने के लिए फ़ाइल सामग्री को कैश करता है। इस सेवा को सिंगलटन के रूप में पंजीकृत किया जाना चाहिए। अन्यथा, कैशिंग अपेक्षित रूप से काम नहीं करेगा।

अच्छे आचरण:

  • यदि सेवा एक राज्य रखती है, तो उसे थ्रेड-सुरक्षित तरीके से उस स्थिति तक पहुंच चाहिए। क्योंकि सभी अनुरोध समवर्ती रूप से सेवा के एक ही उदाहरण का उपयोग करते हैं। मैंने थ्रेड सेफ्टी सुनिश्चित करने के लिए डिक्शनरी के बजाय कॉन्ट्रास्टेरियल का इस्तेमाल किया।
  • सिंगलटन सेवाओं से स्कोप या क्षणिक सेवाओं का उपयोग न करें। क्योंकि, क्षणिक सेवाओं को थ्रेड सुरक्षित होने के लिए डिज़ाइन नहीं किया जा सकता है। यदि आपको उनका उपयोग करना है, तो इन सेवाओं का उपयोग करते समय मल्टी-थ्रेडिंग का ध्यान रखें (उदाहरण के लिए लॉक का उपयोग करें)।
  • मेमोरी लीक आमतौर पर सिंगलटन सेवाओं के कारण होता है। उन्हें आवेदन के अंत तक जारी / निस्तारण नहीं किया जाता है। इसलिए, यदि वे कक्षाओं (या इंजेक्शन) को तुरंत जारी करते हैं, लेकिन उन्हें जारी नहीं करते / निपटान करते हैं, तो वे आवेदन के अंत तक मेमोरी में भी रहेंगे। सुनिश्चित करें कि आप उन्हें सही समय पर रिलीज़ / डिस्पोज़ करें। ऊपर दिए गए विधि बॉडी अनुभाग में रिज़ॉल्विंग सेवाएँ देखें।
  • यदि आप डेटा (इस उदाहरण में फ़ाइल सामग्री) को कैश करते हैं, तो आपको कैश्ड डेटा को अपडेट करने / अमान्य करने के लिए एक तंत्र बनाना चाहिए जब मूल डेटा स्रोत बदलता है (जब इस उदाहरण के लिए डिस्क पर कैश्ड फ़ाइल बदलती है)।

स्कोप्ड सर्विसेज

स्कोपेड लाइफटाइम पहले प्रति वेब अनुरोध डेटा संग्रहीत करने के लिए एक अच्छा उम्मीदवार लगता है। क्योंकि ASP.NET Core प्रति वेब अनुरोध पर एक सेवा क्षेत्र बनाता है। इसलिए, यदि आप किसी सेवा को स्कोप के रूप में पंजीकृत करते हैं, तो इसे वेब अनुरोध के दौरान साझा किया जा सकता है। उदाहरण:

सार्वजनिक वर्ग RequestItemsService
{
    निजी पठनीय शब्दकोश  _items;
    सार्वजनिक अनुरोध
    {
        _items = नया शब्दकोश  ();
    }
    सार्वजनिक शून्य सेट (स्ट्रिंग नाम, ऑब्जेक्ट मान)
    {
        _items [नाम] = मूल्य;
    }
    सार्वजनिक वस्तु प्राप्त करें (स्ट्रिंग नाम)
    {
        वापसी _items [नाम];
    }
}

यदि आप RequestItemsService को स्कोप के रूप में पंजीकृत करते हैं और इसे दो अलग-अलग सेवाओं में इंजेक्ट करते हैं, तो आप एक आइटम प्राप्त कर सकते हैं जिसे किसी अन्य सेवा से जोड़ा जाता है क्योंकि वे समान RequestItemsService उदाहरण साझा करेंगे। यह है कि हम scoped सेवाओं से क्या उम्मीद करते हैं।

लेकिन .. तथ्य हमेशा ऐसा नहीं हो सकता है। यदि आप चाइल्ड सर्विस स्कोप बनाते हैं और चाइल्ड स्कोप से RequestItemsService को हल करते हैं, तो आपको RequestItemsService का एक नया उदाहरण मिलेगा और यह आपकी अपेक्षा के अनुरूप काम नहीं करेगा। तो, स्कोप्ड सेवा का अर्थ हमेशा वेब अनुरोध के अनुसार उदाहरण नहीं होता है।

आप सोच सकते हैं कि आप इस तरह की स्पष्ट गलती नहीं करते हैं (बच्चे के दायरे में एक स्कोप को हल करना)। लेकिन, यह कोई गलती नहीं है (एक बहुत ही नियमित उपयोग) और मामला इतना सरल नहीं हो सकता है। यदि आपकी सेवाओं के बीच एक बड़ी निर्भरता ग्राफ है, तो आप यह नहीं जान सकते हैं कि क्या किसी ने एक चाइल्ड स्कोप बनाया है और एक ऐसी सेवा को हल किया है जो किसी अन्य सेवा को इंजेक्ट करती है ... जो अंत में एक स्कोप्ड सेवा को इंजेक्ट करती है।

अच्छा अभ्यास:

  • एक स्कोप की गई सेवा को एक अनुकूलन के रूप में सोचा जा सकता है जहां इसे वेब अनुरोध में बहुत अधिक सेवाओं द्वारा इंजेक्ट किया जाता है। इस प्रकार, ये सभी सेवाएँ उसी वेब अनुरोध के दौरान सेवा के एक एकल उदाहरण का उपयोग करेंगी।
  • बंद सेवाओं को थ्रेड-सेफ के रूप में डिज़ाइन करने की आवश्यकता नहीं है। क्योंकि, उन्हें सामान्य रूप से एकल वेब-अनुरोध / थ्रेड द्वारा उपयोग किया जाना चाहिए। लेकिन ... उस स्थिति में, आपको विभिन्न थ्रेड्स के बीच सेवा स्कोप साझा नहीं करना चाहिए!
  • यदि आप एक वेब अनुरोध में अन्य सेवाओं के बीच डेटा साझा करने के लिए एक scoped सेवा डिज़ाइन करते हैं तो सावधान रहें (ऊपर समझाया गया है)। आप HttpContext के अंदर प्रति वेब अनुरोध डेटा संग्रहीत कर सकते हैं (इसे एक्सेस करने के लिए IHttpContextAccessor को इंजेक्ट करें) जो कि ऐसा करने का सुरक्षित तरीका है। HttpContext का जीवनकाल समाप्त नहीं हुआ है। वास्तव में, यह डीआई के लिए पंजीकृत नहीं है (इसलिए आप इसे इंजेक्ट नहीं करते हैं, लेकिन इसके बजाय IHttpContextAccessor को इंजेक्ट करें)। HttpContextAccessor कार्यान्वयन एक वेब अनुरोध के दौरान समान HttpContext को साझा करने के लिए AsyncLocal का उपयोग करता है।

निष्कर्ष

निर्भरता इंजेक्शन पहली बार में उपयोग करने के लिए सरल लगता है, लेकिन यदि आप कुछ सख्त सिद्धांतों का पालन नहीं करते हैं तो संभावित बहु-थ्रेडिंग और मेमोरी लीक समस्याएं हैं। मैंने ASP.NET बॉयलरप्लेट ढांचे के विकास के दौरान अपने स्वयं के अनुभवों के आधार पर कुछ अच्छे सिद्धांतों को साझा किया।