An Analysis of Dependency Injection (DI) Containers
डिपेंडेंसी इंजेक्शन (DI) कंटेनर की विवेचना
ASP.NET Core में डिपेंडेंसी इंजेक्शन कंटेनर (जिसे IoC कंटेनर - Inversion of Control Container भी कहा जाता है) एक रनटाइम कंपोनेंट है जो सर्विसेज़ के लाइफसाइकिल को मैनेज करता है और उनके बीच की डिपेंडेंसी को संभालता है।यह कैसे काम करता है:
1. सर्विस रजिस्ट्रेशन: आप कंटेनर को बताते हैं कि "जब भी आपको IMyService की आवश्यकता हो, तो MyServiceImplementation का उपयोग करें।"2. डिपेंडेंसी रिजॉल्यूशन: जब एक क्लास (जैसे एक कंट्रोलर) को अपने कंस्ट्रक्टर में IMyService की आवश्यकता होती है, तो कंटेनर यह पहचानता है कि उसे IMyService की आवश्यकता है। यह तब MyServiceImplementation का एक इंस्टेंस बनाता है (या पुनः उपयोग करता है, लाइफटाइम के आधार पर) और उसे कंट्रोलर के कंस्ट्रक्टर में पास करता है।
3. लाइफसाइकिल प्रबंधन: कंटेनर सर्विस के लाइफटाइम (सिंगलटन, स्कोप, ट्रांजिएंट) को ट्रैक करता है और यह सुनिश्चित करता है कि उन्हें सही समय पर बनाया और डिस्पोज किया जाए।
DI कंटेनर का उपयोग करने के मुख्य लाभ हैं:
1. लूज कपलिंग (Loose Coupling): कंपोनेंट्स एक-दूसरे पर सीधे निर्भर नहीं करते हैं, बल्कि इंटरफेस पर निर्भर करते हैं, जिससे कोड अधिक लचीला और बदलने में आसान हो जाता है।2. बेहतर टेस्टेबिलिटी (Testability): चूंकि डिपेंडेंसी इंजेक्ट की जाती हैं, आप आसानी से टेस्ट के दौरान मॉक या स्टब इम्प्लीमेंटेशन को इंजेक्ट कर सकते हैं, जिससे यूनिट टेस्टिंग आसान हो जाती है।
3. मॉड्यूलरिटी (Modularity): कोड को छोटे, प्रबंधनीय टुकड़ों में तोड़ना आसान हो जाता है।
4. पुन: प्रयोज्यता (Reusability): सर्विसेज़ को विभिन्न कंपोनेंट्स में आसानी से पुन: उपयोग किया जा सकता है।
शब्दावली स्पष्टीकरण
IServiceCollection
परिभाषा: IServiceCollection एक इंटरफेस है जो सर्विस डिस्क्रिप्टर्स (Service Descriptors) के एक कलेक्शन का प्रतिनिधित्व करता है। यह वह जगह है जहाँ आप अपनी एप्लीकेशन की सभी सर्विसेज़ को DI कंटेनर में रजिस्टर करते हैं।भूमिका: यह एक बाल्टी की तरह है जहाँ आप उन सभी नियमों को फेंक देते हैं जो DI कंटेनर को बताते हैं कि कौन सी सर्विस उपलब्ध है और उसे कैसे बनाना है (उसका लाइफटाइम क्या है)।
उपयोग: .NET 6+ में, आप इसे WebApplicationBuilder.Services प्रॉपर्टी के माध्यम से एक्सेस करते हैं।
// Program.cs में
var builder = WebApplication.CreateBuilder(args);
// IServiceCollection के माध्यम से सर्विस रजिस्टर करना
builder.Services.AddSingleton<IMyService, MyServiceImplementation>();
builder.Services.AddScoped<IMyScopedService>(); // अगर TService और TImplementation समान हैं
builder.Services.AddTransient<IOtherService, OtherService>();
// ...
var app = builder.Build();
आप इस इंटरफेस पर विभिन्न एक्सटेंशन मेथड्स (जैसे AddSingleton, AddScoped, AddTransient, AddControllersWithViews, AddDbContext आदि) को कॉल करते हैं ताकि सर्विसेज़ को कॉन्फ़िगर और रजिस्टर किया जा सके।
IServiceProvider
परिभाषा: IServiceProvider एक इंटरफेस है जो सर्विस को रिजॉल्व करने (resolve) या प्राप्त करने के लिए जिम्मेदार है। यह DI कंटेनर का वह हिस्सा है जो आपको रनटाइम पर रजिस्टर की गई सर्विसेज़ के इंस्टेंस प्रदान करता है।भूमिका: एक बार जब सभी सर्विसेज़ IServiceCollection में पंजीकृत हो जाती हैं, तो IServiceCollection को IServiceProvider में "बनाया" जाता है (आमतौर पर builder.Build() कॉल के एक हिस्से के रूप में)। IServiceProvider तब एक फैक्ट्री की तरह कार्य करता है, जो आपको किसी विशिष्ट इंटरफ़ेस या क्लास के लिए कंक्रीट इंस्टेंस प्रदान करता है।
उपयोग: ASP.NET Core स्वयं कंस्ट्रक्टर इंजेक्शन के माध्यम से सर्विसेज़ को स्वचालित रूप से इंजेक्ट करने के लिए IServiceProvider का उपयोग करता है।
बहुत दुर्लभ मामलों में, आपको मैन्युअल रूप से IServiceProvider तक पहुँचने और एक सर्विस को "मैन्युअल रूप से रिजॉल्व" करने की आवश्यकता हो सकती है, हालांकि कंस्ट्रक्टर इंजेक्शन हमेशा पसंदीदा तरीका है।
उदाहरण (केवल दिखावे के लिए, आमतौर पर इसकी सिफारिश नहीं की जाती है):
// किसी कंपोनेंट के अंदर (जैसे एक मिडलवेयर या कभी-कभी एक सर्विस के अंदर)
public class MyComponent
{
private readonly IServiceProvider _serviceProvider;
public MyComponent(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoSomething()
{
// सर्विस को मैन्युअल रूप से प्राप्त करना - बहुत ही कम उपयोग करें
var myService = _serviceProvider.GetService<IMyService>();
// या GetRequiredService अगर सर्विस के मौजूद होने की गारंटी है
var anotherService = _serviceProvider.GetRequiredService<IAnotherService>();
}
}
IServiceProvider vs. IServiceCollection
IServiceProvider यह भी सुनिश्चित करता है कि जब आप GetService<T> या GetRequiredService<T>() का उपयोग करके एक सर्विस का अनुरोध करते हैं, तो यह उस सर्विस के लिए कॉन्फ़िगर किए गए लाइफटाइम नियमों का पालन करता है (सिंगलटन, स्कोप, ट्रांजिएंट)।संक्षेप में, IServiceCollection आपको बताता है कि क्या रजिस्टर करना है और कैसे (लाइफटाइम), जबकि IServiceProvider वह है जो वास्तव में उन रजिस्टर्ड सर्विसेज़ के इंस्टेंस को प्रदान करता है जब उनकी आवश्यकता होती है। ये दोनों मिलकर ASP.NET Core में लचीले और शक्तिशाली डिपेंडेंसी इंजेक्शन सिस्टम का निर्माण करते हैं।
Service Re-registration: Last Registration Wins
ASP.NET Core एप्लीकेशन के अंतर्गत यदि एक सर्विस को रजिस्टर किया गया है और दोबारा उसे रजिस्टर करने का प्रयास करते हैं तब क्या होता है?ASP.NET Core DI कंटेनर में एक सर्विस को एक से ज़्यादा बार रजिस्टर करने पर क्या होता है, यह इस बात पर निर्भर करता है कि आपने उसे किस रजिस्ट्रेशन मेथड (जैसे AddSingleton, AddScoped, AddTransient) का उपयोग करके रजिस्टर किया है।
जब एक सर्विस को दोबारा रजिस्टर किया जाता है तो क्या होता है?
ASP.NET Core का बिल्ट-इन DI कंटेनर अंतिम रजिस्ट्रेशन (Last Registration Wins) के सिद्धांत का पालन करता है, लेकिन इसमें कुछ सूक्ष्म अंतर हैं जो सर्विस के लाइफटाइम पर निर्भर करते हैं।
1. AddSingleton<T> Service, TImplementation() के साथ दोबारा रजिस्ट्रेशन
जब आप एक Singleton सर्विस को दोबारा रजिस्टर करते हैं, तो अंतिम रजिस्ट्रेशन जीत जाता है (Last Registration Wins)। इसका मतलब है कि जब सर्विस को रिजॉल्व किया जाएगा, तो DI कंटेनर उस कंक्रीट इम्प्लीमेंटेशन का इंस्टेंस प्रदान करेगा जो सबसे आखिर में रजिस्टर किया गया था।
उदाहरण:
// Program.cs में
// पहला रजिस्ट्रेशन
builder.Services.AddSingleton<IMyService, MyServiceImplementationA>();
// दूसरा रजिस्ट्रेशन (IMyService को फिर से रजिस्टर करना)
builder.Services.AddSingleton<IMyService, MyServiceImplementationB>();
// जब IMyService को रिजॉल्व किया जाएगा, तो MyServiceImplementationB का एक इंस्टेंस मिलेगा।
// MyServiceImplementationA को कभी भी इंस्टेंशिएट नहीं किया जाएगा।
परिणाम: जब आप IMyService के लिए अनुरोध करेंगे, तो आपको MyServiceImplementationB का एक सिंगलटन इंस्टेंस मिलेगा। MyServiceImplementationA वाला रजिस्ट्रेशन पूरी तरह से ओवरराइट हो जाएगा और इसका उपयोग कभी नहीं किया जाएगा।
2. AddScoped<TService, TImplementation>() के साथ दोबारा रजिस्ट्रेशन
Scoped सर्विस के मामले में भी अंतिम रजिस्ट्रेशन जीतता है (Last Registration Wins)। प्रत्येक HTTP रिक्वेस्ट के लिए, जब सर्विस का अनुरोध किया जाएगा, तो DI कंटेनर उस कंक्रीट इम्प्लीमेंटेशन का इंस्टेंस प्रदान करेगा जो सबसे आखिर में रजिस्टर किया गया था।
उदाहरण:
// Program.cs में
// पहला रजिस्ट्रेशन
builder.Services.AddScoped<IMyService, MyServiceImplementationX>();
// दूसरा रजिस्ट्रेशन
builder.Services.AddScoped<IMyService, MyServiceImplementationY>();
// जब IMyService को किसी रिक्वेस्ट के स्कोप में रिजॉल्व किया जाएगा, तो MyServiceImplementationY का एक इंस्टेंस मिलेगा।
परिणाम: जब आप IMyService के लिए अनुरोध करेंगे, तो आपको MyServiceImplementationY का एक स्कोप्ड इंस्टेंस मिलेगा। MyServiceImplementationX वाला रजिस्ट्रेशन ओवरराइट हो जाएगा।
3. AddTransient<TService, TImplementation>() के साथ दोबारा रजिस्ट्रेशन
Transient सर्विस के मामले में भी अंतिम रजिस्ट्रेशन जीतता है (Last Registration Wins)। जब भी सर्विस का अनुरोध किया जाएगा, तो DI कंटेनर उस कंक्रीट इम्प्लीमेंटेशन का एक नया इंस्टेंस प्रदान करेगा जो सबसे आखिर में रजिस्टर किया गया था।
उदाहरण:
// Program.cs में
// पहला रजिस्ट्रेशन
builder.Services.AddTransient<IMyService, MyServiceImplementationP>();
// दूसरा रजिस्ट्रेशन
builder.Services.AddTransient<IMyService, MyServiceImplementationQ>();
// जब IMyService को रिजॉल्व किया जाएगा, तो MyServiceImplementationQ का एक नया इंस्टेंस मिलेगा।
परिणाम: जब आप IMyService के लिए अनुरोध करेंगे, तो आपको MyServiceImplementationQ का एक
नया ट्रांजिएंट इंस्टेंस मिलेगा। MyServiceImplementationP वाला रजिस्ट्रेशन भी ओवरराइट हो जाएगा। यह क्यों होता है?
ASP.NET Core का बिल्ट-इन DI कंटेनर अपनी IServiceCollection को एक "लिस्ट" के रूप में मानता है। जब आप एक सर्विस को रजिस्टर करते हैं, तो वह इस लिस्ट में एक ServiceDescriptor (जिसमें सर्विस टाइप, इम्प्लीमेंटेशन टाइप और लाइफटाइम की जानकारी होती है) जोड़ता है। जब आप उसी सर्विस टाइप को दोबारा रजिस्टर करते हैं, तो वह बस लिस्ट में एक और ServiceDescriptor जोड़ देता है।
जब IServiceProvider (यानी DI कंटेनर) को किसी सर्विस को रिजॉल्व करना होता है, तो वह आंतरिक रूप से इस लिस्ट के माध्यम से पीछे से आगे (यानी, सबसे हाल ही में जोड़े गए से सबसे पुराने तक) खोजता है। जैसे ही उसे वांछित सर्विस टाइप के लिए पहला मैच मिलता है, वह उसका उपयोग करता है। इसलिए, अंतिम जोड़ा गया रजिस्ट्रेशन प्रभावी हो जाता है।
इसका क्या मतलब है और कब यह मायने रखता है?
ओवरराइटिंग: यह जानना महत्वपूर्ण है कि दोबारा रजिस्ट्रेशन मौजूदा रजिस्ट्रेशन को चुपचाप ओवरराइट कर देता है। कोई त्रुटि या चेतावनी नहीं मिलेगी, जो डिबगिंग को मुश्किल बना सकती है यदि आप गलती से एक सर्विस को दो बार रजिस्टर करते हैं और यह सोचते हैं कि पहला वाला उपयोग किया जा रहा है।
मॉड्यूलरिटी: यह पैटर्न विशेष रूप से मॉड्यूलर एप्लीकेशनों में उपयोगी हो सकता है जहाँ विभिन्न NuGet पैकेज या एप्लीकेशन के हिस्से एक ही इंटरफ़ेस के लिए अपनी स्वयं की डिफ़ॉल्ट इम्प्लीमेंटेशन को रजिस्टर कर सकते हैं। एप्लीकेशन डेवलपर के रूप में, आप हमेशा पैकेज के बाद अपनी खुद की कस्टम इम्प्लीमेंटेशन को रजिस्टर करके उन डिफ़ॉल्ट को ओवरराइड कर सकते हैं।
कॉन्फ़िगरेशन: आप यह सुनिश्चित करने के लिए इस व्यवहार का उपयोग कर सकते हैं कि आपके कोड में विशिष्ट कॉन्फ़िगरेशन डिफ़ॉल्ट रूप से प्रदान किए गए किसी भी पैकेज कॉन्फ़िगरेशन को ओवरराइड करते हैं।
संक्षेप में, ASP.NET Core में सर्विस का दोबारा रजिस्ट्रेशन एक नया रजिस्ट्रेशन बनाता है जो पिछले वाले को प्रभावी रूप से बदल देता है। यह एक जानबूझकर डिजाइन निर्णय है जो कॉन्फ़िगरेशन में लचीलापन प्रदान करता है, जिससे आप आसानी से डिफ़ॉल्ट सर्विस इम्प्लीमेंटेशन को ओवरराइड कर सकते हैं।
क्या ऐसा संभव है जब अगली बार Same सर्विस को रजिस्टर करने का प्रयास किया जाए तब किसी प्रकार का Error मैसेज या कोई वैल्यू एप्लीकेशन को प्राप्त हो?
जी हाँ, यह बिल्कुल संभव है कि जब आप ASP.NET Core एप्लीकेशन में किसी सेम सर्विस को दोबारा रजिस्टर करने की कोशिश करें, तो आपको किसी प्रकार का एरर मैसेज या कोई विशिष्ट वैल्यू मिले, बजाय इसके कि यह चुपचाप ओवरराइट हो जाए।
डिफ़ॉल्ट व्यवहार बनाम कस्टम व्यवहार
जैसा कि हमने पहले चर्चा की, ASP.NET Core का बिल्ट-इन DI कंटेनर डिफ़ॉल्ट रूप से "लास्ट रजिस्ट्रेशन विंस" के सिद्धांत का पालन करता है। इसका मतलब है कि यह कोई एरर या चेतावनी नहीं देता है, बस पुराना रजिस्ट्रेशन नए से ओवरराइट हो जाता है। यह एक डिज़ाइन निर्णय है जो लचीलापन प्रदान करता है (उदाहरण के लिए, थर्ड-पार्टी लाइब्रेरी के डिफ़ॉल्ट को ओवरराइड करना)।हालांकि, अगर आपकी एप्लीकेशन की आवश्यकता है कि एक ही सर्विस को दो बार रजिस्टर करने पर एक समस्या मानी जाए (और आप ऐसा होने से रोकना चाहते हैं), तो आप इसे निम्न तरीकों से प्राप्त कर सकते हैं:
1. मैन्युअल चेक और कस्टम एक्सटेंशन मेथड बनाना
आप IServiceCollection के लिए एक कस्टम एक्सटेंशन मेथड बना सकते हैं जो सर्विस रजिस्टर करने से पहले यह जांचे कि क्या सर्विस पहले से ही पंजीकृत है। यदि ऐसा है, तो यह एक InvalidOperationException फेंक सकता है या एक बूलियन मान लौटा सकता है।उदाहरण:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; // TryAddSingleton, TryAddScoped, TryAddTransient के लिए
public static class ServiceCollectionExtensions
{
// एक नया कस्टम रजिस्ट्रेशन मेथड जो चेक करता है
public static IServiceCollection AddSingletonUnique<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
// चेक करें कि क्या TService पहले से ही पंजीकृत है
if (services.Any(s => s.ServiceType == typeof(TService)))
{
throw new InvalidOperationException($"Service of type {typeof(TService).FullName} is already registered. Duplicate registration not allowed.");
}
services.AddSingleton<TService, TImplementation>();
return services;
}
// आप AddScopedUnique और AddTransientUnique भी बना सकते हैं
}
// Program.cs में इसका उपयोग कैसे करें
var builder = WebApplication.CreateBuilder(args);
// यह ठीक काम करेगा
builder.Services.AddSingletonUnique<IMyService, MyServiceImplementationA>();
// यह एक InvalidOperationException फेंकेगा
// builder.Services.AddSingletonUnique<IMyService, MyServiceImplementationB>();
var app = builder.Build();
// ...
app.Run();
फायदे:
- यह आपको पूरी तरह से नियंत्रण देता है कि आप कैसे एरर हैंडलिंग करना चाहते हैं।
- यह स्पष्ट रूप से बताता है कि आपका इरादा डुप्लिकेट रजिस्ट्रेशन को रोकना है।
- आपको हर उस रजिस्ट्रेशन मेथड के लिए कस्टम एक्सटेंशन मेथड बनाना होगा जिसे आप नियंत्रित करना चाहते हैं (AddSingletonUnique, AddScopedUnique, AddTransientUnique आदि)।
- यह बिल्ट-इन मेथड्स को बदलने के लिए अतिरिक्त कोड है।
2. TryAdd* एक्सटेंशन मेथड्स का उपयोग करना
Microsoft ने Microsoft.Extensions.DependencyInjection.Extensions नेमस्पेस में कुछ उपयोगी एक्सटेंशन मेथड्स प्रदान किए हैं, जैसे TryAddSingleton(), TryAddScoped(), और TryAddTransient()। ये मेथड्स केवल तभी सर्विस को रजिस्टर करते हैं जब वह सर्विस टाइप अभी तक पंजीकृत न हो। यदि सर्विस पहले से ही पंजीकृत है, तो ये मेथड्स कुछ नहीं करते हैं और कोई एरर भी नहीं फेंकते हैं।उदाहरण:
// Program.cs में
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; // TryAdd* मेथड्स के लिए
var builder = WebApplication.CreateBuilder(args);
// यह सफलतापूर्वक रजिस्टर होगा
builder.Services.TryAddSingleton<IMyService, MyServiceImplementationA>();
Console.WriteLine("IMyService (A) registered successfully.");
// यह कुछ नहीं करेगा क्योंकि IMyService पहले से ही पंजीकृत है। कोई एरर नहीं।
builder.Services.TryAddSingleton<IMyService, MyServiceImplementationB>();
Console.WriteLine("IMyService (B) attempted, but likely not registered.");
var app = builder.Build();
// टेस्ट करने के लिए
app.MapGet("/myservice", (IMyService myService) => myService.GetType().Name);
app.Run();
// आउटपुट: MyServiceImplementationA (क्योंकि B रजिस्टर नहीं हुआ)
फायदे:
- यह एक क्लीनर और बिल्ट-इन तरीका है यह सुनिश्चित करने का कि एक सर्विस केवल एक बार ही पंजीकृत हो।
- कोई अपवाद नहीं फेंका जाता है, जो कुछ परिदृश्यों में वांछनीय हो सकता है।
- यह कोई एरर या चेतावनी नहीं देता है, जिसका अर्थ है कि अगर आपको उम्मीद थी कि दूसरा रजिस्ट्रेशन प्रभावी होगा, तो आपको पता नहीं चलेगा कि ऐसा नहीं हुआ है। आपको यह सुनिश्चित करने के लिए लॉगिंग या अन्य निरीक्षण करने की आवश्यकता होगी कि कौन सा इम्प्लीमेंटेशन वास्तव में उपयोग किया जा रहा है।
- यह "लास्ट रजिस्ट्रेशन विंस" व्यवहार को बदलता है, जिसे कभी-कभी मॉड्यूलरिटी के लिए वांछित किया जाता है।
3. कस्टम DI कंटेनर का उपयोग करना (एडवांस्ड)
ASP.NET Core एक प्लगगेबल DI कंटेनर आर्किटेक्चर का समर्थन करता है। इसका मतलब है कि आप Microsoft के बिल्ट-इन कंटेनर को बदलकर तीसरे पक्ष के DI कंटेनर जैसे Autofac, DryIoc, Ninject आदि का उपयोग कर सकते हैं। इनमें से कई कंटेनर डुप्लिकेट रजिस्ट्रेशन को संभालने के लिए विभिन्न कॉन्फ़िगरेशन विकल्प प्रदान करते हैं, जिनमें डुप्लिकेट होने पर एरर फेंकना भी शामिल है।फायदे:
- अधिक शक्तिशाली और कॉन्फ़िगरेबल DI क्षमताएं।
- कुछ कंटेनर अधिक उन्नत फीचर्स (जैसे इंटरसेप्शन, ऑटो-रजिस्ट्रेशन) प्रदान करते हैं।
- आपके प्रोजेक्ट में एक अतिरिक्त निर्भरता (NuGet पैकेज) जुड़ जाती है।
- सीखने की अवस्था (learning curve) हो सकती है क्योंकि प्रत्येक कंटेनर की अपनी API और कॉन्फ़िगरेशन होती है।
निष्कर्ष
- यदि आपका लक्ष्य एरर मैसेज प्राप्त करना या डुप्लिकेट रजिस्ट्रेशन को रोकना है, तो कस्टम Add*Unique एक्सटेंशन मेथड्स बनाना सबसे सीधा और नियंत्रित तरीका है।
- यदि आपका लक्ष्य बस यह सुनिश्चित करना है कि सर्विस केवल एक बार ही पंजीकृत हो (और डुप्लिकेट प्रयासों को चुपचाप अनदेखा किया जाए), तो TryAdd* मेथड्स एक उत्कृष्ट बिल्ट-इन समाधान हैं।
- चूंकि .NET का डिफ़ॉल्ट व्यवहार "लास्ट रजिस्ट्रेशन विंस" है, इसलिए आपको यह तय करना होगा कि क्या आपकी एप्लीकेशन को इस व्यवहार से विचलित होने और डुप्लिकेट रजिस्ट्रेशन के लिए एक सख्त नीति लागू करने की आवश्यकता है।
टिप्पणियाँ
एक टिप्पणी भेजें