Asp.Net कोर में एक ही इंटरफ़ेस के कई कार्यान्वयन कैसे पंजीकृत करें?


239

मेरे पास ऐसी सेवाएं हैं जो एक ही इंटरफ़ेस से ली गई हैं।

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { } 
public class ServiceC : IService { }

आमतौर पर, अन्य IoC कंटेनर Unityआपको कुछ Keyअलग करके ठोस कार्यान्वयन दर्ज करने की अनुमति देते हैं।

ASP.NET Core में, मैं इन सेवाओं को कैसे पंजीकृत करूँ और कुछ समय के आधार पर इन्हें रनटाइम में हल करूँ?

मुझे ऐसी कोई Addसेवा विधियाँ नहीं दिखतीं जो एक पैरामीटर keyया nameपैरामीटर लेती हों , जो आमतौर पर ठोस कार्यान्वयन को अलग करने के लिए उपयोग की जाती हैं।

    public void ConfigureServices(IServiceCollection services)
    {            
         // How do I register services of the same interface?            
    }


    public MyController:Controller
    {
       public void DoSomething(string key)
       { 
          // How do I resolve the service by key?
       }
    }

क्या फ़ैक्टरी पैटर्न यहाँ एकमात्र विकल्प है?

अपडेट 1
मैं हालांकि यहां लेख गया हूं जो दिखाता है कि जब हम कई ठोस कार्यान्वयन करते हैं, तो सेवा उदाहरण प्राप्त करने के लिए फ़ैक्टरी पैटर्न का उपयोग कैसे करें। हालाँकि, यह अभी भी एक पूर्ण समाधान नहीं है। जब मैं _serviceProvider.GetService()विधि को कॉल करता हूं, तो मैं कंस्ट्रक्टर में डेटा इंजेक्ट नहीं कर सकता।

उदाहरण के लिए इस पर विचार करें:

public class ServiceA : IService
{
     private string _efConnectionString;
     ServiceA(string efconnectionString)
     {
       _efConnecttionString = efConnectionString;
     } 
}

public class ServiceB : IService
{    
   private string _mongoConnectionString;
   public ServiceB(string mongoConnectionString)
   {
      _mongoConnectionString = mongoConnectionString;
   }
}

public class ServiceC : IService
{    
    private string _someOtherConnectionString
    public ServiceC(string someOtherConnectionString)
    {
      _someOtherConnectionString = someOtherConnectionString;
    }
}

_serviceProvider.GetService()उपयुक्त कनेक्शन स्ट्रिंग को कैसे इंजेक्ट कर सकते हैं ? एकता, या किसी अन्य IoC पुस्तकालय में, हम उस प्रकार के पंजीकरण में कर सकते हैं। मैं IOption का उपयोग कर सकता हूं , हालांकि, मुझे सभी सेटिंग्स को इंजेक्ट करने की आवश्यकता होगी। मैं किसी विशेष कनेक्शन स्ट्रिंग को सेवा में इंजेक्ट नहीं कर सकता।

यह भी ध्यान दें कि मैं अन्य कंटेनरों (एकता सहित) का उपयोग करने से बचने की कोशिश कर रहा हूं, क्योंकि तब मुझे नए कंटेनर के साथ और सब कुछ (जैसे, नियंत्रक) को पंजीकृत करना होगा।

इसके अलावा, सर्विस इंस्ट्रूमेंट बनाने के लिए फैक्ट्री पैटर्न का उपयोग करना डीआईपी के खिलाफ है, क्योंकि यह उन क्लाइंट की निर्भरता की संख्या को बढ़ाता है, जिनके यहां विवरण है

इसलिए, मुझे लगता है कि ASP.NET Core में डिफॉल्ट डि को दो चीजें याद आ रही हैं:

  1. एक कुंजी का उपयोग करके उदाहरणों को पंजीकृत करने की क्षमता
  2. पंजीकरण के दौरान निर्माणकर्ताओं में स्थैतिक डेटा को इंजेक्ट करने की क्षमता


2
नाम-आधारित पंजीकरणों के लिए आखिरकार नगेट में एक विस्तार है , आशा है कि यह मदद कर सकता है
21

नमस्ते, मेरे मूर्खतापूर्ण प्रश्न के लिए क्षमा करें, लेकिन मैं Microsoft के साथ नए बारे में हूं। Extensions.D dependencyInjection ... क्या आपको लगता है कि 3 खाली इंटरफेस बनाएं जो "सार्वजनिक इंटरफ़ेस IServiceA: IService" जैसे Iservice का विस्तार करते हैं और "सार्वजनिक वर्ग ServiceA: IServiceA" की तुलना में "... एक अच्छा अभ्यास विकल्प हो सकता है?
एमिलियानो माग्लीओका

क्या यह किसी काम का लेख है? stevejgordon.co.uk/…
माइक बी

Update1एक अलग प्रश्न में ले जाया जा सकता है क्योंकि निर्माणकर्ताओं में चीजों को इंजेक्ट करना, काम करने से बहुत अलग है जो निर्माण के लिए है
नील

जवाबों:


245

मैंने एक सरल वर्कअराउंड का उपयोग किया Funcजब मैंने खुद को इस स्थिति में पाया तो मैंने किया।

सबसे पहले एक साझा प्रतिनिधि घोषित करें:

public delegate IService ServiceResolver(string key);

फिर अपने में Startup.cs, कई ठोस पंजीकरण और उन प्रकारों की एक मैनुअल मैपिंग सेटअप करें:

services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();

services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
    switch (key)
    {
        case "A":
            return serviceProvider.GetService<ServiceA>();
        case "B":
            return serviceProvider.GetService<ServiceB>();
        case "C":
            return serviceProvider.GetService<ServiceC>();
        default:
            throw new KeyNotFoundException(); // or maybe return null, up to you
    }
});

और डीआई से पंजीकृत किसी भी वर्ग से इसका उपयोग करें:

public class Consumer
{
    private readonly IService _aService;

    public Consumer(ServiceResolver serviceAccessor)
    {
        _aService = serviceAccessor("A");
    }

    public void UseServiceA()
    {
        _aService.DoTheThing();
    }
}

ध्यान रखें कि इस उदाहरण में संकल्प के लिए कुंजी एक स्ट्रिंग है, सादगी के लिए और क्योंकि ओपी विशेष रूप से इस मामले के लिए पूछ रहा था।

लेकिन आप कुंजी के रूप में किसी भी कस्टम रिज़ॉल्यूशन प्रकार का उपयोग कर सकते हैं, क्योंकि आप आमतौर पर अपने कोड को घुमाते हुए एक विशाल एन-केस स्विच नहीं चाहते हैं। इस बात पर निर्भर करता है कि आपका ऐप कैसा है।


1
@MatthewStevenMonkan ने एक उदाहरण के साथ मेरे उत्तर को अपडेट किया
मिगुएल ए। अरिल्ला जूल

2
इस तरह के एक कारखाने के पैटर्न का उपयोग करना सबसे अच्छा तरीका है। साझा करने के लिए धन्यवाद!
सेर्गेई अकोपोव

2
+1 बहुत साफ और स्वच्छ, क्योंकि जब हम अन्य डी-कंटेनर का उपयोग करते हैं तो हमें उनके पैकेज को शामिल करना होगा जब भी हमें निर्भरता को हल करने की आवश्यकता होती है, जैसे। AutoFac में ILifetimeScope।
अनुपम सिंह

1
@AnupamSingh मेरी राय में, .NET कोर पर चलने वाले अधिकांश छोटे से मध्यम अनुप्रयोगों को किसी भी DI फ्रेमवर्क की आवश्यकता नहीं है, बस जटिलता और अवांछित निर्भरता को जोड़ता है, अंतर्निहित DI की सुंदरता और सादगी पर्याप्त से अधिक है, और यह यह भी आसानी से बढ़ाया जा सकता है।
मिगुएल ए। अरिल्ला

7
डाउन वोट स्पष्टीकरण - इसका बहुत दिलचस्प है, लेकिन मैं वर्तमान में इस फंक जादू को दूर करने के लिए एक बड़े पैमाने पर कोड बेस को रिफ्लेक्ट कर रहा हूं, जो कुछ साल पहले (एमएस डीआई क्रांति से पहले) किया गया था, इसके साथ समस्या यह है कि यह गुणों पर नाटकीयता जटिलता को बढ़ाता है जो आगे डीआई रेजोल्यूशन को लाइन के नीचे ले जा सकता है। उदाहरण के लिए मैंने एक विंडोज सर्विस हैंडलर पर काम किया था जिसमें फंक के साथ करने के लिए कोड की 1.6k से अधिक लाइनें थीं और इसे करने के बाद DI के सुझाए गए तरीके से मैंने इसे 0.2k लाइनों तक घटा दिया। ओके-लाइन्स ऑफ़ कोड का मतलब कुछ भी नहीं है .. सिवाय इसके पढ़ने और फिर से आसान करने के अलावा ...
Piotr Kula

79

एक अन्य विकल्प विस्तार विधि का उपयोग करना GetServicesहै Microsoft.Extensions.DependencyInjection

अपनी सेवाओं को इस प्रकार पंजीकृत करें:

services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();

फिर थोड़ा Linq के साथ हल करें:

var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));

या

var serviceZ = services.First(o => o.Name.Equals("Z"));

(यह मानते हुए कि IService"नाम" नामक एक स्ट्रिंग संपत्ति है)

पक्का कर लो using Microsoft.Extensions.DependencyInjection;

अपडेट करें

AspNet 2.1 स्रोत: GetServices


6
निश्चित नहीं है, लेकिन मुझे लगता है कि यह नियतात्मक नहीं है। आज आपको मिलने वाला कोई भी परिणाम कल बदल सकता है, यह एक अच्छा अभ्यास नहीं है।
rnrneverdies

4
GetServices के लिंक के लिए upvote, जिसने मुझे दिखाया कि आप सेवाओं की सूची का अनुरोध करके एक आश्रित सेवा का अनुरोध कर सकते हैंIEnumerable<IService>
जॉनी 5

20
serviceProvider.GetServices <IService> () ServiceA, ServiceB और ServiceC में से प्रत्येक को इंस्टेंट कर देगा। आप केवल एक ही सेवा के निर्माता को कॉल करना चाहेंगे - वह जो आपको वास्तव में चाहिए। यह एक बड़ी समस्या है यदि कार्यान्वयन हल्के वजन के नहीं हैं या आपके पास IService के कई कार्यान्वयन हैं (उदाहरण के लिए, आपके पास प्रत्येक मॉडल के लिए IRepository का स्वतः-जनित कार्यान्वयन है)।
उरोस

6
मैं @Uros से सहमत हूं। यह एक अच्छा उपाय नहीं है। कल्पना कीजिए कि यदि आप 10 IService- कार्यान्वयन रजिस्टर करते हैं और जो उदाहरण आपको वास्तव में चाहिए वह अंतिम एक है। इस मामले में, 9 उदाहरण वास्तव में DI द्वारा बनाए गए हैं, जिनका उपयोग कभी नहीं किया जाता है।
थोमै

4
बुरा विचार: कई अप्रयुक्त उदाहरण, सेवा लोकेटर विरोधी पैटर्न और वास्तविक कार्यान्वयन के लिए प्रत्यक्ष युग्मन (टाइपो <ServiceA>)।
रिको Suter

20

यह द्वारा समर्थित नहीं है Microsoft.Extensions.DependencyInjection

लेकिन आप एक अन्य निर्भरता इंजेक्शन तंत्र में प्लग-इन कर सकते हैं, जैसे StructureMap देखें यह होम पेज है और यह GitHub प्रोजेक्ट है

यह बिल्कुल मुश्किल नहीं है:

  1. अपने में एक संरचना स्ट्रक्चर पर निर्भरता जोड़ें project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. इसे अंदर ASP.NET पाइपलाइन में इंजेक्ट करें ConfigureServicesऔर अपनी कक्षाएं पंजीकृत करें (डॉक्स देखें)

    public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider !
    {
        // Add framework services.
        services.AddMvc();
        services.AddWhatever();
    
        //using StructureMap;
        var container = new Container();
        container.Configure(config =>
        {
            // Register stuff in container, using the StructureMap APIs...
            config.For<IPet>().Add(new Cat("CatA")).Named("A");
            config.For<IPet>().Add(new Cat("CatB")).Named("B");
            config.For<IPet>().Use("A"); // Optionally set a default
            config.Populate(services);
        });
    
        return container.GetInstance<IServiceProvider>();
    }
  3. फिर, एक नामित उदाहरण प्राप्त करने के लिए, आपको अनुरोध करने की आवश्यकता होगी IContainer

    public class HomeController : Controller
    {
        public HomeController(IContainer injectedContainer)
        {
            var myPet = injectedContainer.GetInstance<IPet>("B");
            string name = myPet.Name; // Returns "CatB"

बस।

निर्माण के लिए उदाहरण के लिए, आपको चाहिए

    public interface IPet
    {
        string Name { get; set; }
    }

    public class Cat : IPet
    {
        public Cat(string name)
        {
            Name = name;
        }

        public string Name {get; set; }
    }

मैंने इस दृष्टिकोण की कोशिश की है, लेकिन मुझे अपने नियंत्रक पर रनटाइम त्रुटियाँ मिलती हैं क्योंकि IContainer बिल्ड योजनाओं में नहीं मिलता है। क्या ऐसा कुछ है, जिसके लिए मुझे IContainer को ऑटो-इंजेक्ट करने की आवश्यकता है?
मुहूर्त

BTW, मैं StructureMap.Micorosoft.D dependencyInjection 1.3.0 का उपयोग कर रहा हूं।
मुहूर्त

क्या आप कन्फिगर्स सर्विसेस में नया कंटेनर लौटा रहे हैं?
गेरार्डो ग्रिग्नोली

जैसा कि ऊपर # चरण 2 में इंगित किया गया है, मैं नए कंटेनर का IServiceProviderInstance लौटा रहा हूं। मैंने नकल की कि वास्तव में केवल इसे मेरे प्रकारों के लिए बदल रहा है। यह एक अच्छा समाधान है और पूरी तरह से काम कर रहा है। एकमात्र दोष यह है कि मैं एक इंजेक्टेड कंटेनर का उपयोग करने में असमर्थ हूं और एक स्थिर कंटेनर का सहारा ले रहा हूं, जिसे मैं नहीं करना चाहता।
मुहूर्त

1
इसके लिए मेरे काम करता है धन्यवाद GerardoGrignoli। @mohrtan नमूना कोड यहाँ है यदि आप अभी भी इस पर गौर कर रहे हैं। github.com/Yawarmurtaza/AspNetCoreStructureMap
यवर मुर्तजा

13

आप सही हैं, ASP.NET कोर कंटेनर में निर्मित कई सेवाओं को पंजीकृत करने और फिर एक विशिष्ट प्राप्त करने की अवधारणा नहीं है, जैसा कि आप सुझाव देते हैं, एक कारखाना उस मामले में एकमात्र वास्तविक समाधान है।

वैकल्पिक रूप से, आप किसी तीसरे पक्ष के कंटेनर जैसे कि एकता या स्ट्रक्चर्सपाइप पर स्विच कर सकते हैं जो आपको आवश्यक समाधान प्रदान करता है (यहां दस्तावेज: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html/replacing- (डिफ़ॉल्ट-सेवाओं-कंटेनर )।


13

मैंने एक ही मुद्दे का सामना किया है और यह साझा करना चाहता हूं कि मैंने इसे कैसे हल किया और क्यों।

जैसा कि आपने बताया कि दो समस्याएं हैं। सबसे पहला:

Asp.Net Core में मैं इन सेवाओं को कैसे पंजीकृत करूं और इसे कुछ समय के आधार पर रनटाइम में हल करूं?

तो हमारे पास क्या विकल्प हैं? दो लोगों का सुझाव है:

  • एक कस्टम फैक्ट्री (जैसे _myFactory.GetServiceByKey(key)) का उपयोग करें

  • अन्य DI इंजन का उपयोग करें (जैसे _unityContainer.Resolve<IService>(key))

क्या फ़ैक्टरी पैटर्न यहाँ एकमात्र विकल्प है?

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

तो क्या विकल्प बेहतर है? यहाँ मैं @Sock से सहमत हूँ जिन्होंने कस्टम फैक्टरी का उपयोग करने का सुझाव दिया था, और इसीलिए।

सबसे पहले, मैं हमेशा नई निर्भरता को जोड़ने से बचने की कोशिश करता हूं जब उन्हें वास्तव में ज़रूरत नहीं होती है। तो मैं इस बात में आपसे सहमत हूँ। इसके अलावा, दो डीआई फ्रेमवर्क का उपयोग करना कस्टम कारखाना अमूर्त बनाने से भी बदतर है। दूसरे मामले में आपको नया पैकेज निर्भरता (जैसे एकता) जोड़ना होगा लेकिन एक नए कारखाने के इंटरफेस के आधार पर यहां कम बुराई है। ASP.NET Core DI का मुख्य विचार, मेरा मानना ​​है कि यह सरलता है। यह निम्नलिखित विशेषताएं का एक न्यूनतम सेट का कहना है KISS सिद्धांत । यदि आपको कुछ अतिरिक्त सुविधा की आवश्यकता है तो DIY या एक संगत प्लंजिन का उपयोग करें वांछित सुविधा को लागू करता है (ओपन बंद सिद्धांत)।

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

दूसरी समस्या:

_ServiceProvider.GetService () उचित कनेक्शन स्ट्रिंग को कैसे इंजेक्ट कर सकता है?

सबसे पहले, मैं आपके साथ सहमत हूं कि नई चीजों पर निर्भर करता है जैसे IOptions(और इसलिए पैकेज पर Microsoft.Extensions.Options.ConfigurationExtensions) एक अच्छा विचार नहीं है। मैंने कुछ चर्चा करते हुए देखा है IOptionsकि इसके लाभ के बारे में अलग-अलग राय कहाँ थी। फिर, मैं नई निर्भरता को जोड़ने से बचने की कोशिश करता हूं जब उन्हें वास्तव में जरूरत नहीं होती है। क्या वाकई इसकी जरूरत है? मेरे ख़्याल से नहीं। अन्यथा प्रत्येक कार्यान्वयन को उस कार्यान्वयन से आने वाली किसी भी स्पष्ट आवश्यकता के बिना उस पर निर्भर होना होगा (मेरे लिए यह आईएसपी के उल्लंघन की तरह दिखता है, जहां मैं आपसे भी सहमत हूं)। यह कारखाने के आधार पर भी सही है लेकिन इस मामले में इसे टाला जा सकता है।

ASP.NET Core DI उस उद्देश्य के लिए बहुत अच्छा अधिभार प्रदान करता है:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));

नमस्ते, मेरे मूर्खतापूर्ण प्रश्न के लिए खेद है, लेकिन मैं Microsoft के साथ नया हूँ। Extensions.D dependencyInjection ... क्या आपको लगता है कि 3 इंटरफेस बनाते हैं जो "सार्वजनिक इंटरफ़ेस IServiceA: IService" जैसे Iservice का विस्तार करते हैं और "सार्वजनिक वर्ग ServiceA: ISISA" से ... क्या एक अच्छा अभ्यास विकल्प हो सकता है?
एमिलियानो मैगलियोका

1
@ एमिलियानो-मैग्लिओका सामान्य तौर पर, आपको IServiceAअपने मामले में (आईएसपी) का उपयोग करने वाले इंटरफेस पर निर्भर नहीं होना चाहिए । चूँकि आप IServiceकेवल तरीकों का उपयोग कर रहे हैं , आपके पास IServiceकेवल निर्भरता होनी चाहिए ।
नीलीस

1
@ cagatay-kalan ओपी के प्रश्न के मामले में वह आसानी से ASP.NET कोर DI के साथ अपने लक्ष्य को प्राप्त कर सकता है। अन्य DI फ्रेमवर्क की कोई आवश्यकता नहीं है।
नीलीस

1
@EmilianoMagliocca इसे आसानी से इस तरह से हल किया जा सकता है: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));पहली कक्षा के services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));लिए और दूसरे के लिए।
नीलीस

1
@EmilianoMagliocca मेरे उदाहरण में 'MyFirstClass' और 'MySecondClass' में इंटरफ़ेस प्रकार के समान ctor पैरामीटर हैं, जो Escpos और Usbpos दोनों लागू होते हैं। तो ऊपर कोड केवल IoC कंटेनर को निर्देश देता है कि 'MyFirstClass' और 'MySecondClass' को कैसे स्थापित किया जाए। और कुछ नहीं। इसलिए इसके अलावा आपको 'MyFirstClass' और 'MySecondClass' के लिए कुछ अन्य इंटरफ़ेस (मानचित्रों) को मैप करने की आवश्यकता हो सकती है। यह आपकी आवश्यकताओं पर निर्भर करता है और मैंने इसे अपने उदाहरण में शामिल नहीं किया है।
नीलीस

13

मैं बस एक IEnumerable इंजेक्षन

Startup.cs में कॉन्फ़िगर करें

Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
                {
                    services.AddScoped(typeof(IService), t);
                });

सेवाएं फ़ोल्डर

public interface IService
{
    string Name { get; set; }
}

public class ServiceA : IService
{
    public string Name { get { return "A"; } }
}

public class ServiceB : IService
{    
    public string Name { get { return "B"; } }
}

public class ServiceC : IService
{    
    public string Name { get { return "C"; } }
}

MyController.cs

public class MyController
{
    private readonly IEnumerable<IService> _services;
    public MyController(IEnumerable<IService> services)
    {
        _services = services;
    }
    public void DoSomething()
    {
        var service = _services.Where(s => s.Name == "A").Single();
    }
...
}

Extensions.cs

    public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
    {
        return assembly.GetTypesAssignableFrom(typeof(T));
    }
    public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
    {
        List<Type> ret = new List<Type>();
        foreach (var type in assembly.DefinedTypes)
        {
            if (compareType.IsAssignableFrom(type) && compareType != type)
            {
                ret.Add(type);
            }
        }
        return ret;
    }

नियंत्रक की DoSomething () पद्धति में आप इच्छित सेवा को हल करने के लिए टाइपोफ़ का उपयोग कर सकते हैं: var सेवा = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
सियारन ब्रूएन

मैंने सचमुच सब कुछ करने की कोशिश की, और यह एकमात्र समाधान है जिसने मेरे लिए काम किया। धन्यवाद!
Skatz1990

@ Skatz1990 नीचे दिए गए समाधान को किसी अन्य पोस्ट में आज़माएं। मुझे लगता है कि यह क्लीनर और उपयोग करने के लिए सरल है।
टी ब्राउन

12

यहां अधिकांश उत्तर एकल जिम्मेदारी सिद्धांत का उल्लंघन करते हैं (एक सेवा वर्ग को स्वयं निर्भरता का समाधान नहीं करना चाहिए) और / या सेवा लोकेटर विरोधी पैटर्न का उपयोग करना चाहिए।

इन समस्याओं से बचने का एक और विकल्प है:

  • इंटरफ़ेस पर एक अतिरिक्त सामान्य प्रकार के पैरामीटर का उपयोग करें या गैर-सामान्य इंटरफ़ेस को लागू करने वाले एक नए इंटरफ़ेस,
  • मार्कर प्रकार और फिर जोड़ने के लिए एक एडेप्टर / इंटरसेप्टर वर्ग को लागू करें
  • "नाम" के रूप में सामान्य प्रकार का उपयोग करें

मैंने और अधिक विवरण के साथ एक लेख लिखा है: .NET में निर्भरता इंजेक्शन: गुम पंजीकरणों के आसपास काम करने का एक तरीका


कैसे स्वीकार किया जवाब एकल जिम्मेदारी सिद्धांत violets?
1813 पर एलपी 13

की टिप्पणी देखें stackoverflow.com/a/52066039/876814 और यह भी स्वीकार किए जाते हैं जवाब में सेवा lazyily हल हो गई है, यानी आप केवल पता है कि यह क्रम में विफल रहता है और वहाँ स्थिर कंटेनर निर्माण के बाद स्टार्टअप पर यह जांच करने के लिए कोई रास्ता नहीं है (के समान है टिप्पणी में जवाब)। SRP क्योंकि सेवा न केवल अपने व्यावसायिक तर्क के लिए बल्कि निर्भरता संकल्प के लिए भी जिम्मेदार है
रिको Suter

@RicoSuter मुझे वास्तव में आपके ब्लॉग में समाधान पसंद है, लेकिन स्टार्टअप क्लास के भीतर आपके DI द्वारा भ्रमित किया गया है। विशिष्ट रूप से, मैं उस लाइन मैसेजप्रीजर ("MyOrderCreatedQueue") को नहीं समझता हूं क्योंकि मुझे उस हस्ताक्षर के साथ एक कंस्ट्रक्टर नहीं दिखता है। सेवाएं।
ली जेड

धन्यवाद, लेख को अद्यतन किया और IMMagePublisher के नमूने के कार्यान्वयन के रूप में MyMessagePublisher का उपयोग करें
रिको

7

इस पार्टी में थोड़ी देर हो गई, लेकिन यहाँ मेरा समाधान है: ...

यदि सामान्य हैंडलर के लिए Startup.cs या Program.cs ...

services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();

टी इंटरफ़ेस सेटअप के IMyInterface

public interface IMyInterface<T> where T : class, IMyInterface<T>
{
    Task Consume();
}

टी के IMyInterface के ठोस कार्यान्वयन

public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
    public async Task Consume();
}

public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
    public async Task Consume();
}

उम्मीद है कि अगर इस तरह से कोई मुद्दा है, तो कोई यह बताएगा कि ऐसा करने का गलत तरीका क्यों है।


2
IMyInterface<CustomerSavedConsumer>और IMyInterface<ManagerSavedConsumer>कर रहे हैं विभिन्न सेवा प्रकार - यह सब पर ऑप्स सवाल का जवाब नहीं है।
रिचर्ड हैर

2
ओपी Asp.net कोर में एक ही इंटरफ़ेस के कई कार्यान्वयन को पंजीकृत करने का एक तरीका चाहता था। अगर मैंने ऐसा नहीं किया, तो कृपया बताएं कि कैसे (वास्तव में)।
ग्रे

1
जब आप सही होते हैं, तो यह पैटर्न उस प्रभाव को अनुमति देता है जो सेशन चाहता था। कम से कम जब मैं खुद को ऐसा करने की कोशिश कर रहा था तो मैं इस पद पर ठोकर खा गया और मेरे समाधान ने मेरी स्थिति के लिए सबसे अच्छा काम किया।
ग्रे

1
मुझे उम्मीद है कि यह मुद्दा अधिक था कि एकल इंटरफ़ेस (एमएस डि का उपयोग करके) के लिए कई कार्यान्वयन को पंजीकृत करने से कंटेनर को एक कार्यान्वयन को दूसरे से अलग करने की अनुमति नहीं मिलती है। अन्य DIs में आप उन्हें कुंजी दे सकते हैं ताकि कंटेनर को पता चले कि किसे चुनना है। एमएस में आप है एक प्रतिनिधि का उपयोग करें और मैन्युअल रूप से चयन करने के लिए। आपका समाधान इस परिदृश्य को संबोधित नहीं करता है क्योंकि आपके इंटरफेस अलग हैं, इसलिए कंटेनर में सही कार्यान्वयन को चुनने का कोई मुद्दा नहीं है। जबकि आपका नमूना स्पष्ट रूप से काम करता है, यह समस्या के लिए समाधान नहीं है जैसा कि कहा गया है।
रिचर्ड होउर

3
@Gray भले ही आपके पोस्ट को कुछ बुरा दबाया गया, मैं इस समाधान को आगे बढ़ाने के लिए धन्यवाद देता हूं। यह .net cores DI में सीमाओं को दूर करने के लिए पाठकों को एक और विकल्प देता है। हालांकि यह सीधे ओपी प्रश्न का उत्तर नहीं दे सकता है, यह एक सही वैकल्पिक समाधान प्रदान करता है, जो कि एसओ के बारे में है, सही है?
नील वॉटसन

5

जाहिर है, आप बस अपने सेवा इंटरफ़ेस के IEnumerable इंजेक्षन कर सकते हैं! और फिर वह उदाहरण ढूंढें जिसे आप LINQ का उपयोग करना चाहते हैं।

मेरा उदाहरण एडब्ल्यूएस एसएनएस सेवा के लिए है, लेकिन आप वास्तव में किसी भी इंजेक्शन सेवा के लिए भी ऐसा कर सकते हैं।

चालू होना

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}

services.AddSingleton<ISNSFactory, SNSFactory>();

services.Configure<SNSConfig>(Configuration);

SNSConfig

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",

एसएनएस फैक्ट्री

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;

    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }

    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }

    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }

    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }

    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);

        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }

        return service;
    }
}

public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();

    IAmazonSimpleNotificationService ForSMS();
}

अब आप उस क्षेत्र के लिए एसएनएस सेवा प्राप्त कर सकते हैं जिसे आप अपनी कस्टम सेवा या नियंत्रक में चाहते हैं

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;

    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }

    .......
 }

public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;

    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }

     .........
}

5

एक कारखाना दृष्टिकोण निश्चित रूप से व्यवहार्य है। एक अन्य दृष्टिकोण है कि IService से विरासत में आने वाले व्यक्तिगत इंटरफेस बनाने के लिए विरासत का उपयोग करना, अपने IService कार्यान्वयन में विरासत में दिए गए इंटरफेस को लागू करना, और आधार के बजाय विरासत में दिए गए इंटरफेस को पंजीकृत करना। एक वंशानुक्रम पदानुक्रम या कारखानों को जोड़ना "सही" पैटर्न है, यह सब इस बात पर निर्भर करता है कि आप किससे बात करते हैं। मुझे अक्सर इस पैटर्न का उपयोग करना पड़ता है जब एक ही अनुप्रयोग में कई डेटाबेस प्रदाताओं के साथ काम करता है, जैसे कि जेनेरिक का उपयोग करता हैIRepository<T> डेटा एक्सेस की नींव के रूप में ।

उदाहरण इंटरफेस और कार्यान्वयन:

public interface IService 
{
}

public interface IServiceA: IService
{}

public interface IServiceB: IService
{}

public IServiceC: IService
{}

public class ServiceA: IServiceA 
{}

public class ServiceB: IServiceB
{}

public class ServiceC: IServiceC
{}

कंटेनर:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();

5

Necromancing।
मुझे लगता है कि यहां लोग पहिया को फिर से मजबूत कर रहे हैं - और बुरी तरह से, अगर मैं ऐसा कह सकता हूं ...
यदि आप कुंजी द्वारा एक घटक पंजीकृत करना चाहते हैं, तो बस एक शब्दकोश का उपयोग करें:

System.Collections.Generic.Dictionary<string, IConnectionFactory> dict = 
    new System.Collections.Generic.Dictionary<string, IConnectionFactory>(
        System.StringComparer.OrdinalIgnoreCase);

dict.Add("ReadDB", new ConnectionFactory("connectionString1"));
dict.Add("WriteDB", new ConnectionFactory("connectionString2"));
dict.Add("TestDB", new ConnectionFactory("connectionString3"));
dict.Add("Analytics", new ConnectionFactory("connectionString4"));
dict.Add("LogDB", new ConnectionFactory("connectionString5"));

और फिर सेवा-संग्रह के साथ शब्दकोश को पंजीकृत करें:

services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);

यदि आप तब शब्दकोश प्राप्त करने के लिए तैयार नहीं हैं और इसे कुंजी द्वारा एक्सेस करना चाहते हैं, तो आप सेवा-संग्रह में एक अतिरिक्त कुंजी-लुकअप-विधि जोड़कर शब्दकोश छिपा सकते हैं:
(प्रतिनिधि / बंद का उपयोग एक संभावित अनुचर को मौका देना चाहिए) यह समझना कि क्या चल रहा है - तीर-संकेतन थोड़ा गूढ़ है)

services.AddTransient<Func<string, IConnectionFactory>>(
    delegate (IServiceProvider sp)
    {
        return
            delegate (string key)
            {
                System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService
 <System.Collections.Generic.Dictionary<string, IConnectionFactory>>(sp);

                if (dbs.ContainsKey(key))
                    return dbs[key];

                throw new System.Collections.Generic.KeyNotFoundException(key); // or maybe return null, up to you
            };
    });

अब आप या तो अपने प्रकारों तक पहुँच सकते हैं

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection

या

System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection

जैसा कि हम देख सकते हैं, पहला वाला सिर्फ पूरी तरह से अति-विशिष्ट है, क्योंकि आप क्लोज़र और AddTransient (और यदि आप VB का उपयोग करते हैं, तो बिना ब्रेसिज़ भी अलग होंगे) की आवश्यकता के बिना, शब्दकोश के साथ ठीक वही कर सकते हैं:

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection

(सरल बेहतर है - आप इसे विस्तार विधि के रूप में उपयोग करना चाह सकते हैं)

बेशक, अगर आपको शब्दकोश पसंद नहीं है, तो आप अपने इंटरफ़ेस को एक संपत्ति Name(या जो भी) के साथ तैयार कर सकते हैं , और कुंजी द्वारा देख सकते हैं:

services.AddSingleton<IConnectionFactory>(new ConnectionFactory("ReadDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("WriteDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("TestDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("Analytics"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("LogDB"));



// /programming/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core
services.AddTransient<Func<string, IConnectionFactory>>(
    delegate(IServiceProvider sp)
    {
        return
            delegate(string key)
            {
                System.Collections.Generic.IEnumerable<IConnectionFactory> svs = 
                    sp.GetServices<IConnectionFactory>();

                foreach (IConnectionFactory thisService in svs)
                {
                    if (key.Equals(thisService.Name, StringComparison.OrdinalIgnoreCase))
                        return thisService;
                }

                return null;
            };
    });

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

ये सिर्फ मेरे $ 0.05 हैं


यदि सेवा IDisposeलागू हो गई है, तो सेवा का निपटान करने के लिए कौन जिम्मेदार है? आपने डिक्शनरी को पंजीकृत किया हैSingleton
एलपी 13

@ LP13: आप एक प्रतिनिधि के रूप में मूल्य के रूप में भी शब्दकोश पंजीकृत कर सकते हैं, फिर आप इसे इट्रांसेन्ट में पंजीकृत कर सकते हैं, और एक नया उदाहरण बना सकते हैं, जैसे। GetRequiredService <T> () ["logDB"] ()
स्टीफन स्टीगर

5

अपनी पोस्ट के बाद से, मैं एक जेनेरिक फैक्ट्री क्लास में चला गया हूँ

प्रयोग

 services.AddFactory<IProcessor, string>()
         .Add<ProcessorA>("A")
         .Add<ProcessorB>("B");

 public MyClass(IFactory<IProcessor, string> processorFactory)
 {
       var x = "A"; //some runtime variable to select which object to create
       var processor = processorFactory.Create(x);
 }

कार्यान्वयन

public class FactoryBuilder<I, P> where I : class
{
    private readonly IServiceCollection _services;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public FactoryBuilder(IServiceCollection services)
    {
        _services = services;
        _factoryTypes = new FactoryTypes<I, P>();
    }
    public FactoryBuilder<I, P> Add<T>(P p)
        where T : class, I
    {
        _factoryTypes.ServiceList.Add(p, typeof(T));

        _services.AddSingleton(_factoryTypes);
        _services.AddTransient<T>();
        return this;
    }
}
public class FactoryTypes<I, P> where I : class
{
    public Dictionary<P, Type> ServiceList { get; set; } = new Dictionary<P, Type>();
}

public interface IFactory<I, P>
{
    I Create(P p);
}

public class Factory<I, P> : IFactory<I, P> where I : class
{
    private readonly IServiceProvider _serviceProvider;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public Factory(IServiceProvider serviceProvider, FactoryTypes<I, P> factoryTypes)
    {
        _serviceProvider = serviceProvider;
        _factoryTypes = factoryTypes;
    }

    public I Create(P p)
    {
        return (I)_serviceProvider.GetService(_factoryTypes.ServiceList[p]);
    }
}

एक्सटेंशन

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DependencyExtensions
    {
        public static IServiceCollection AddFactory<I, P>(this IServiceCollection services, Action<FactoryBuilder<I, P>> builder)
            where I : class
        {
            services.AddTransient<IFactory<I, P>, Factory<I, P>>();
            var factoryBuilder = new FactoryBuilder<I, P>(services);
            builder(factoryBuilder);
            return services;
        }
    }
}

क्या आप प्रदान कर सकते हैं। संशोधन () विधि विलोपन?
डेवलपर

क्षमा करें बस यह देखा ... जोड़ा
T ब्राउन

3

जबकि ऐसा लगता है कि @ मिगेल ए। अरिला ने इसे स्पष्ट रूप से इंगित किया है और मैंने उसके लिए मतदान किया है, मैंने उसके उपयोगी समाधान के शीर्ष पर एक और समाधान बनाया है जो साफ-सुथरा दिखता है लेकिन इसके लिए बहुत अधिक काम की आवश्यकता होती है।

यह निश्चित रूप से उपरोक्त समाधान पर निर्भर करता है। इसलिए मूल रूप से मैंने इसके समान कुछ बनाया Func<string, IService>>और मैंने इसे IServiceAccessorएक इंटरफ़ेस के रूप में बुलाया और फिर मुझे कुछ और एक्सटेंशन जोड़ने पड़े IServiceCollectionजैसे:

public static IServiceCollection AddSingleton<TService, TImplementation, TServiceAccessor>(
            this IServiceCollection services,
            string instanceName
        )
            where TService : class
            where TImplementation : class, TService
            where TServiceAccessor : class, IServiceAccessor<TService>
        {
            services.AddSingleton<TService, TImplementation>();
            services.AddSingleton<TServiceAccessor>();
            var provider = services.BuildServiceProvider();
            var implementationInstance = provider.GetServices<TService>().Last();
            var accessor = provider.GetServices<TServiceAccessor>().First();

            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(TServiceAccessor));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }

            accessor.SetService(implementationInstance, instanceName);
            services.AddSingleton<TServiceAccessor>(prvd => accessor);
            return services;
        }

सेवा एक्सेसर ऐसा दिखता है:

 public interface IServiceAccessor<TService>
    {
         void Register(TService service,string name);
         TService Resolve(string name);

    }

अंतिम परिणाम, आप सेवाओं को नाम या नाम के उदाहरणों के साथ पंजीकृत करने में सक्षम होंगे जैसे कि हम अन्य कंटेनरों के साथ करते थे..इस प्रकार:

    services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
    services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");

यह अब के लिए पर्याप्त है, लेकिन अपने काम को पूरा करने के लिए, अधिक विस्तार विधियों को जोड़ना बेहतर है क्योंकि आप एक ही दृष्टिकोण के बाद सभी प्रकार के पंजीकरणों को कवर कर सकते हैं।

स्टैकओवरफ्लो पर एक और पोस्ट था, लेकिन मैं इसे नहीं पा सकता हूं, जहां पोस्टर ने विवरणों में समझाया है कि यह सुविधा क्यों समर्थित नहीं है और इसके चारों ओर कैसे काम करना है, मूल रूप से @Miguel के जैसा कहा गया है। यह अच्छी पोस्ट थी, हालांकि मैं प्रत्येक बिंदु से सहमत नहीं हूं क्योंकि मुझे लगता है कि ऐसी परिस्थितियां हैं जहां आपको वास्तव में नामित उदाहरणों की आवश्यकता है। मैं उस लिंक को यहाँ पोस्ट करूँगा जब मैं इसे दोबारा पा लूँगा।

तथ्य के रूप में, आपको उस चयनकर्ता या एक्सेसर को पास करने की आवश्यकता नहीं है:

मैं अपनी परियोजना में निम्नलिखित कोड का उपयोग कर रहा हूं और इसने अब तक अच्छा काम किया है।

 /// <summary>
    /// Adds the singleton.
    /// </summary>
    /// <typeparam name="TService">The type of the t service.</typeparam>
    /// <typeparam name="TImplementation">The type of the t implementation.</typeparam>
    /// <param name="services">The services.</param>
    /// <param name="instanceName">Name of the instance.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection AddSingleton<TService, TImplementation>(
        this IServiceCollection services,
        string instanceName
    )
        where TService : class
        where TImplementation : class, TService
    {
        var provider = services.BuildServiceProvider();
        var implementationInstance = provider.GetServices<TService>().LastOrDefault();
        if (implementationInstance.IsNull())
        {
            services.AddSingleton<TService, TImplementation>();
            provider = services.BuildServiceProvider();
            implementationInstance = provider.GetServices<TService>().Single();
        }
        return services.RegisterInternal(instanceName, provider, implementationInstance);
    }

    private static IServiceCollection RegisterInternal<TService>(this IServiceCollection services,
        string instanceName, ServiceProvider provider, TService implementationInstance)
        where TService : class
    {
        var accessor = provider.GetServices<IServiceAccessor<TService>>().LastOrDefault();
        if (accessor.IsNull())
        {
            services.AddSingleton<ServiceAccessor<TService>>();
            provider = services.BuildServiceProvider();
            accessor = provider.GetServices<ServiceAccessor<TService>>().Single();
        }
        else
        {
            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(IServiceAccessor<TService>));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }
        }
        accessor.Register(implementationInstance, instanceName);
        services.AddSingleton<TService>(prvd => implementationInstance);
        services.AddSingleton<IServiceAccessor<TService>>(prvd => accessor);
        return services;
    }

    //
    // Summary:
    //     Adds a singleton service of the type specified in TService with an instance specified
    //     in implementationInstance to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
    //
    // Parameters:
    //   services:
    //     The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service
    //     to.
    //   implementationInstance:
    //     The instance of the service.
    //   instanceName:
    //     The name of the instance.
    //
    // Returns:
    //     A reference to this instance after the operation has completed.
    public static IServiceCollection AddSingleton<TService>(
        this IServiceCollection services,
        TService implementationInstance,
        string instanceName) where TService : class
    {
        var provider = services.BuildServiceProvider();
        return RegisterInternal(services, instanceName, provider, implementationInstance);
    }

    /// <summary>
    /// Registers an interface for a class
    /// </summary>
    /// <typeparam name="TInterface">The type of the t interface.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection As<TInterface>(this IServiceCollection services)
         where TInterface : class
    {
        var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
        if (descriptor.IsNotNull())
        {
            var provider = services.BuildServiceProvider();
            var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
            services?.AddSingleton(implementationInstance);
        }
        return services;
    }

1
इसने मेरी समस्या को हल करने में मदद की जहां मैं सेवा एक्सेसर में प्रकारों का पंजीकरण खो रहा था। ट्रिक सर्विस एक्सेसर के लिए सभी बाइंडिंग को हटाने और फिर इसे जोड़ने के लिए थी!
उमर फारूक ख्वाजा

3

मैंने इसके लिए एक पुस्तकालय बनाया है जो कुछ अच्छी सुविधाओं को लागू करता है। कोड GitHub पर पाया जा सकता है: https://github.com/dazinator/Dazinator.Extensions.D dependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DeencyInjection/

उपयोग सीधा है:

  1. अपनी परियोजना में Dazinator.Extensions.D dependencyInjection nuget पैकेज जोड़ें।
  2. अपने नामांकित सेवा पंजीकरण जोड़ें।
    var services = new ServiceCollection();
    services.AddNamed<AnimalService>(names =>
    {
        names.AddSingleton("A"); // will resolve to a singleton instance of AnimalService
        names.AddSingleton<BearService>("B"); // will resolve to a singleton instance of BearService (which derives from AnimalService)
        names.AddSingleton("C", new BearService()); will resolve to singleton instance provided yourself.
        names.AddSingleton("D", new DisposableTigerService(), registrationOwnsInstance = true); // will resolve to singleton instance provided yourself, but will be disposed for you (if it implements IDisposable) when this registry is disposed (also a singleton).

        names.AddTransient("E"); // new AnimalService() every time..
        names.AddTransient<LionService>("F"); // new LionService() every time..

        names.AddScoped("G");  // scoped AnimalService
        names.AddScoped<DisposableTigerService>("H");  scoped DisposableTigerService and as it implements IDisposable, will be disposed of when scope is disposed of.

    });

ऊपर दिए गए उदाहरण में, ध्यान दें कि प्रत्येक नामित पंजीकरण के लिए, आप आजीवन या सिंगलटन, स्कोप्ड, या क्षणिक को भी निर्दिष्ट कर रहे हैं।

आप सेवाओं को दो तरीकों में से एक में हल कर सकते हैं, यह इस बात पर निर्भर करता है कि क्या आप अपनी सेवाओं के साथ इस पैकेज पर निर्भरता लेने में सहज हैं:

public MyController(Func<string, AnimalService> namedServices)
{
   AnimalService serviceA = namedServices("A");
   AnimalService serviceB = namedServices("B"); // BearService derives from AnimalService
}

या

public MyController(NamedServiceResolver<AnimalService> namedServices)
{
   AnimalService serviceA = namedServices["A"];
   AnimalService serviceB = namedServices["B"]; // instance of BearService returned derives from AnimalService
}

मैंने विशेष रूप से Microsoft के साथ काम करने के लिए इस लाइब्रेरी को डिज़ाइन किया है। उदाहरण के लिए निर्भरता पर निर्भरता - उदाहरण के लिए:

  1. जब आप नामित सेवाओं को पंजीकृत करते हैं, तो आपके द्वारा पंजीकृत किसी भी प्रकार के पैरामीटर के साथ कंस्ट्रक्टर हो सकते हैं - वे डीआई के माध्यम से संतुष्ट होंगे, उसी तरह से AddTransient<>, AddScoped<>और AddSingleton<>विधियां आमतौर पर काम करती हैं।

  2. क्षणिक और स्कोप नामांकित सेवाओं के लिए, रजिस्ट्री एक ObjectFactoryऐसा निर्माण करती है जिससे वह जरूरत पड़ने पर बहुत जल्दी टाइप के नए उदाहरणों को सक्रिय कर सके। यह अन्य दृष्टिकोणों की तुलना में बहुत तेज़ है और Microsoft के अनुरूप है।


2

क्या यह मूल्य के लिए मेरा समाधान है ... कैसल विंडसर पर स्विच करने पर विचार किया गया क्योंकि मैं यह नहीं कह सकता कि मुझे उपरोक्त समाधान पसंद आया। माफ़ करना!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

अपने विभिन्न कार्यान्वयन बनाएँ

public class YourClassA : IStage<YouClassA> { 
    public void DoSomething() 
    {
        ...TODO
    }
}

public class YourClassB : IStage<YourClassB> { .....etc. }

पंजीकरण

services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()

निर्माता और उदाहरण का उपयोग ...

public class Whatever
{
   private IStage ClassA { get; }

   public Whatever(IStage<YourClassA> yourClassA)
   {
         ClassA = yourClassA;
   }

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }

1

@Rnrneverdies के समाधान का विस्तार। ToString () के बजाय, निम्नलिखित विकल्पों का भी उपयोग किया जा सकता है- 1) सामान्य संपत्ति कार्यान्वयन के साथ, 2) @ क्रेग ब्रुनेट्टी द्वारा सुझाई गई सेवाओं की एक सेवा।

public interface IService { }
public class ServiceA : IService
{
    public override string ToString()
    {
        return "A";
    }
}

public class ServiceB : IService
{
    public override string ToString()
    {
        return "B";
    }

}

/// <summary>
/// extension method that compares with ToString value of an object and returns an object if found
/// </summary>
public static class ServiceProviderServiceExtensions
{
    public static T GetService<T>(this IServiceProvider provider, string identifier)
    {
        var services = provider.GetServices<T>();
        var service = services.FirstOrDefault(o => o.ToString() == identifier);
        return service;
    }
}

public void ConfigureServices(IServiceCollection services)
{
    //Initials configurations....

    services.AddSingleton<IService, ServiceA>();
    services.AddSingleton<IService, ServiceB>();
    services.AddSingleton<IService, ServiceC>();

    var sp = services.BuildServiceProvider();
    var a = sp.GetService<IService>("A"); //returns instance of ServiceA
    var b = sp.GetService<IService>("B"); //returns instance of ServiceB

    //Remaining configurations....
}

1

यहाँ और लेखों के उत्तर पढ़ने के बाद मैं इसे बिना तार के काम कर पाने में सक्षम था। जब आपके पास एक ही इंटरफ़ेस के कई कार्यान्वयन होते हैं, तो DI इन्हें एक संग्रह में जोड़ देगा, इसलिए इसका उपयोग करके उस संस्करण को पुनः प्राप्त करना संभव है जिसे आप संग्रह से चाहते हैं typeof

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(IService, ServiceA);
    services.AddScoped(IService, ServiceB);
    services.AddScoped(IService, ServiceC);
}

// Any class that uses the service(s)
public class Consumer
{
    private readonly IEnumerable<IService> _myServices;

    public Consumer(IEnumerable<IService> myServices)
    {
        _myServices = myServices;
    }

    public UseServiceA()
    {
        var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
        serviceA.DoTheThing();
    }

    public UseServiceB()
    {
        var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
        serviceB.DoTheThing();
    }

    public UseServiceC()
    {
        var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
        serviceC.DoTheThing();
    }
}

आईओसी के उद्देश्य को हराता है। आप शायद यह भी लिख सकते हैं:var serviceA = new ServiceA();
जेम्स क्यूरन

2
@JamesCurran नहीं अगर ServiceA की निर्भरता है, या यदि आप इकाई का परीक्षण करना चाहते हैं।
जोर्न

0

हालांकि बॉक्स कार्यान्वयन से बाहर यह पेशकश नहीं करता है, यहां एक नमूना परियोजना है जो आपको नामित उदाहरणों को पंजीकृत करने की अनुमति देती है, और फिर अपने कोड में INamedServiceFactory इंजेक्षन करें और नाम से उदाहरणों को बाहर निकालें। यहां अन्य प्रकार के समाधानों के विपरीत, यह आपको एक ही कार्यान्वयन के कई उदाहरणों को पंजीकृत करने की अनुमति देगा लेकिन अलग तरीके से कॉन्फ़िगर किया गया है

https://github.com/macsux/DotNetDINamedInstances


0

सेवाओं के लिए एक सेवा के बारे में कैसे?

यदि हमारे पास एक INamedService इंटरफ़ेस (.Name संपत्ति के साथ) है, तो हम एक IServiceCollection एक्सटेंशन .GetService (स्ट्रिंग नाम) के लिए लिख सकते हैं, जहां एक्सटेंशन उस स्ट्रिंग पैरामीटर को ले जाएगा, और स्वयं पर .GetServices () और प्रत्येक में लौटे। उदाहरण, वह उदाहरण ढूंढें जिसका INamedService.Name दिए गए नाम से मेल खाता है।

ऐशे ही:

public interface INamedService
{
    string Name { get; }
}

public static T GetService<T>(this IServiceProvider provider, string serviceName)
    where T : INamedService
{
    var candidates = provider.GetServices<T>();
    return candidates.FirstOrDefault(s => s.Name == serviceName);
}

इसलिए, आपके IMyService को INamedService को लागू करना होगा, लेकिन आपको कुंजी-आधारित रिज़ॉल्यूशन मिलेगा जो आप चाहते हैं, ठीक है?

निष्पक्ष होने के लिए, यह इनमेडसेव्स इंटरफ़ेस भी बदसूरत लगता है, लेकिन अगर आप आगे जाना चाहते हैं और चीजों को और अधिक सुरुचिपूर्ण बनाना चाहते हैं, तो कार्यान्वयन / वर्ग पर एक [NamedServiceAttribute ("ए") कोड द्वारा पाया जा सकता है। विस्तार, और यह भी काम करेगा। और भी अधिक निष्पक्ष होने के लिए, प्रतिबिंब धीमा है, इसलिए एक अनुकूलन क्रम में हो सकता है, लेकिन ईमानदारी से यह है कि डीआई इंजन के साथ मदद करनी चाहिए। TCO के लिए गति और सादगी प्रत्येक भव्य योगदानकर्ता हैं।

सब सब में, एक स्पष्ट कारखाने की कोई आवश्यकता नहीं है, क्योंकि "एक नामित सेवा ढूंढना" ऐसी पुन: प्रयोज्य अवधारणा है, और कारखाने की कक्षाएं समाधान के रूप में पैमाने पर नहीं होती हैं। और एक फंक <> ठीक लगता है, लेकिन एक स्विच ब्लॉक बहुत ब्लाह है , और फिर से, आप जितनी बार आप फैक्ट्रीज लिख रहे हैं, उतनी बार फंक लिखेंगे। कम कोड के साथ सरल, पुन: प्रयोज्य शुरू करें, और यदि वह फिर से ऐसा नहीं करता है, तो जटिल हो जाएं।


2
इसे सेवा लोकेटर पैटर्न कहा जाता है और आमतौर पर जाने के लिए सबसे अच्छा मार्ग नहीं है जब तक कि आपके पास पूरी तरह से न हो
जो फिलिप्स

@JoePhillips क्या आपके पास कुछ इनपुट है कि इसका अच्छा समाधान क्यों नहीं है? मुझे इसकी लालित्य बहुत पसंद है। केवल नकारात्मक पक्ष मैं यह सोच सकता हूं कि मैं उन सभी का उदाहरण बनाता हूं जो आपको मिलते हैं।
पीटर

2
@ पेटर इसका मुख्य कारण है क्योंकि इसके साथ काम करना बहुत कठिन है। यदि आप किसी क्लास में एक सर्विस लोकेटर ऑब्जेक्ट से गुजर रहे हैं, तो यह बिल्कुल स्पष्ट नहीं है कि क्लास उन सभी निर्भरताओं का उपयोग करता है क्योंकि यह उन सभी को एक जादू "भगवान" ऑब्जेक्ट से मिल रहा है। आप जिस प्रकार को बदलना चाहते हैं, उसके संदर्भ खोजने की कल्पना करें। जब आप सेवा लोकेटर ऑब्जेक्ट के माध्यम से सब कुछ प्राप्त कर रहे हों तो यह क्षमता मूल रूप से गायब हो जाती है। कंस्ट्रक्टर इंजेक्शन कहीं अधिक स्पष्ट और विश्वसनीय है
जो फिलिप्स

मुझे नही पता। स्पष्टता मेरे लिए शून्य नहीं है ... क्योंकि अगर मैंने इस बात का ध्यान रखा कि मेरे घटक अपनी निर्भरता का लाभ कैसे उठाते हैं, तो मेरे पास इसके लिए इकाई परीक्षण होंगे ... परीक्षण जो न केवल प्रत्येक निर्भरता का उल्लेख करते हैं, बल्कि हमें समझने में मदद करते हैं कैसे प्रत्येक निर्भरता की जरूरत है। आप कैसे और क्या करने जा रहे हैं, इसके बारे में रचनाकारों को पढ़कर?
क्रेग ब्रुनेट्टी

0

मैं एक ही समस्या में चला गया हूं और मैंने नामांकित सेवाओं को अनुमति देने के लिए एक सरल विस्तार के साथ काम किया है। आप इसे यहां देख सकते हैं:

यह आपको जितनी चाहें उतनी (नामांकित) सेवाओं को जोड़ने की अनुमति देता है:

 var serviceCollection = new ServiceCollection();
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), "A", ServiceLifetime.Transient);
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), "B", ServiceLifetime.Transient);

 var serviceProvider = serviceCollection.BuildServiceProvider();

 var myServiceA = serviceProvider.GetService<IMyService>("A");
 var myServiceB = serviceProvider.GetService<IMyService>("B");

पुस्तकालय आपको इस तरह से "फ़ैक्टरी पैटर्न" को आसानी से लागू करने की अनुमति देता है:

    [Test]
    public void FactoryPatternTest()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), MyEnum.A.GetName(), ServiceLifetime.Transient);
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), MyEnum.B.GetName(), ServiceLifetime.Transient);

        serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var factoryPatternResolver = serviceProvider.GetService<IMyServiceFactoryPatternResolver>();

        var myServiceA = factoryPatternResolver.Resolve(MyEnum.A);
        Assert.NotNull(myServiceA);
        Assert.IsInstanceOf<MyServiceA>(myServiceA);

        var myServiceB = factoryPatternResolver.Resolve(MyEnum.B);
        Assert.NotNull(myServiceB);
        Assert.IsInstanceOf<MyServiceB>(myServiceB);
    }

    public interface IMyServiceFactoryPatternResolver : IFactoryPatternResolver<IMyService, MyEnum>
    {
    }

    public class MyServiceFactoryPatternResolver : FactoryPatternResolver<IMyService, MyEnum>, IMyServiceFactoryPatternResolver
    {
        public MyServiceFactoryPatternResolver(IServiceProvider serviceProvider)
        : base(serviceProvider)
        {
        }
    }

    public enum MyEnum
    {
        A = 1,
        B = 2
    }

आशा करता हूँ की ये काम करेगा


0

मैंने IServiceCollectionउपयोग WithNameकिए गए एक्सटेंशन पर अपना स्वयं का एक्सटेंशन बनाया :

public static IServiceCollection AddScopedWithName<TService, TImplementation>(this IServiceCollection services, string serviceName)
        where TService : class
        where TImplementation : class, TService
    {
        Type serviceType = typeof(TService);
        Type implementationServiceType = typeof(TImplementation);
        ServiceCollectionTypeMapper.Instance.AddDefinition(serviceType.Name, serviceName, implementationServiceType.AssemblyQualifiedName);
        services.AddScoped<TImplementation>();
        return services;
    }

ServiceCollectionTypeMapperएक सिंगलटन उदाहरण नक्शे है कि IService> NameOfService> Implementationजहां एक अंतरफलक अलग-अलग नामों के साथ कई कार्यान्वयन हो सकता था, इस से हम हल जब वी की जरूरत है और संकल्प कई सेवाओं की तुलना में एक अलग दृष्टिकोण का चयन करने के लिए हम क्या चाहते है कर सकते हैं प्रकार के रजिस्टर करने के लिए अनुमति देता है।

 /// <summary>
/// Allows to set the service register mapping.
/// </summary>
public class ServiceCollectionTypeMapper
{
    private ServiceCollectionTypeMapper()
    {
        this.ServiceRegister = new Dictionary<string, Dictionary<string, string>>();
    }

    /// <summary>
    /// Gets the instance of mapper.
    /// </summary>
    public static ServiceCollectionTypeMapper Instance { get; } = new ServiceCollectionTypeMapper();

    private Dictionary<string, Dictionary<string, string>> ServiceRegister { get; set; }

    /// <summary>
    /// Adds new service definition.
    /// </summary>
    /// <param name="typeName">The name of the TService.</param>
    /// <param name="serviceName">The TImplementation name.</param>
    /// <param name="namespaceFullName">The TImplementation AssemblyQualifiedName.</param>
    public void AddDefinition(string typeName, string serviceName, string namespaceFullName)
    {
        if (this.ServiceRegister.TryGetValue(typeName, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out _))
            {
                throw new InvalidOperationException($"Exists an implementation with the same name [{serviceName}] to the type [{typeName}].");
            }
            else
            {
                services.Add(serviceName, namespaceFullName);
            }
        }
        else
        {
            Dictionary<string, string> serviceCollection = new Dictionary<string, string>
            {
                { serviceName, namespaceFullName },
            };
            this.ServiceRegister.Add(typeName, serviceCollection);
        }
    }

    /// <summary>
    /// Get AssemblyQualifiedName of implementation.
    /// </summary>
    /// <typeparam name="TService">The type of the service implementation.</typeparam>
    /// <param name="serviceName">The name of the service.</param>
    /// <returns>The AssemblyQualifiedName of the inplementation service.</returns>
    public string GetService<TService>(string serviceName)
    {
        Type serviceType = typeof(TService);

        if (this.ServiceRegister.TryGetValue(serviceType.Name, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out string serviceImplementation))
            {
                return serviceImplementation;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

एक नई सेवा पंजीकृत करने के लिए:

services.AddScopedWithName<IService, MyService>("Name");

सेवा को हल करने के लिए हमें IServiceProviderइस तरह के विस्तार की आवश्यकता है ।

/// <summary>
    /// Gets the implementation of service by name.
    /// </summary>
    /// <typeparam name="T">The type of service.</typeparam>
    /// <param name="serviceProvider">The service provider.</param>
    /// <param name="serviceName">The service name.</param>
    /// <returns>The implementation of service.</returns>
    public static T GetService<T>(this IServiceProvider serviceProvider, string serviceName)
    {
        string fullnameImplementation = ServiceCollectionTypeMapper.Instance.GetService<T>(serviceName);
        if (fullnameImplementation == null)
        {
            throw new InvalidOperationException($"Unable to resolve service of type [{typeof(T)}] with name [{serviceName}]");
        }
        else
        {
            return (T)serviceProvider.GetService(Type.GetType(fullnameImplementation));
        }
    }

जब हल:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

याद रखें कि सर्विसप्रोइडर को हमारे एप्लिकेशन में एक कंस्ट्रक्टर के भीतर इंजेक्ट किया जा सकता है IServiceProvider

आशा है कि ये आपकी मदद करेगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.