วิธีการลงทะเบียนการใช้งานหลาย ๆ อินเทอร์เฟซเดียวกันใน Asp.Net Core


239

ฉันมีบริการที่ได้มาจากอินเทอร์เฟซเดียวกัน

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

โดยทั่วไปแล้วคอนเทนเนอร์ IoC อื่น ๆ เช่นUnityอนุญาตให้คุณลงทะเบียนการใช้งานที่เป็นรูปธรรมโดยบางส่วนKeyที่แตกต่าง

ใน ASP.NET Core ฉันจะลงทะเบียนบริการเหล่านี้อย่างไรและแก้ไขได้ที่ runtime ตามคีย์บางตัว

ฉันไม่เห็น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?
       }
    }

รูปแบบจากโรงงานเป็นตัวเลือกเดียวที่นี่หรือไม่

Update1
ฉันได้ไป แต่บทความที่นี่ที่แสดงให้เห็นถึงวิธีการที่จะใช้รูปแบบโรงงานที่จะได้รับบริการกรณีเมื่อเรามีการใช้งานที่เป็นรูปธรรมหลาย อย่างไรก็ตามมันก็ยังไม่เป็นทางออกที่สมบูรณ์ เมื่อฉันเรียก_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()ฉีดสตริงการเชื่อมต่อที่เหมาะสมได้อย่างไร ใน Unity หรือไลบรารี IoC อื่น ๆ เราสามารถทำได้ที่การลงทะเบียนประเภท ฉันสามารถใช้IOptionได้ แต่นั่นจะทำให้ฉันต้องฉีดการตั้งค่าทั้งหมด ฉันไม่สามารถฉีดสตริงการเชื่อมต่อเฉพาะลงในบริการได้

โปรดทราบว่าฉันกำลังพยายามหลีกเลี่ยงการใช้คอนเทนเนอร์อื่น ๆ (รวมถึง Unity) เพราะฉันต้องลงทะเบียนทุกอย่าง (เช่นตัวควบคุม) ด้วยคอนเทนเนอร์ใหม่เช่นกัน

นอกจากนี้การใช้รูปแบบโรงงานที่จะสร้างอินสแตนซ์บริการกับกรมทรัพย์สินทางปัญญาเป็นมันจะเพิ่มจำนวนการอ้างอิงลูกค้ามีรายละเอียดที่นี่

ดังนั้นฉันคิดว่า DI เริ่มต้นใน ASP.NET Core ขาดสองสิ่ง:

  1. ความสามารถในการลงทะเบียนอินสแตนซ์โดยใช้กุญแจ
  2. ความสามารถในการฉีดข้อมูลคงที่ลงในตัวสร้างระหว่างการลงทะเบียน


2
ในที่สุดก็มีการขยายตัวใน nugetสำหรับการลงทะเบียนตามชื่อหวังว่ามันจะช่วยได้
neleus

สวัสดีขอโทษสำหรับคำถามโง่ ๆ ของฉัน แต่ฉันเป็นเรื่องใหม่กับ Microsoft.Extensions.DependencyInject ... คุณคิดว่าสร้างอินเทอร์เฟซว่างเปล่า 3 รายการที่ขยาย Iservice เช่น "ส่วนต่อประสานสาธารณะสาธารณะ IServiceA: IService" และมากกว่า "ชั้นสาธารณะ ServiceA: IServiceA "... อาจเป็นตัวเลือกในการฝึกฝนที่ดี?
Emiliano Magliocca

บทความนี้มีประโยชน์อะไรบ้าง? stevejgordon.co.uk/…
Mike B

สามารถUpdate1ย้ายไปยังคำถามที่แตกต่างกันเนื่องจากการฉีดสิ่งต่าง ๆ ในตัวสร้างแตกต่างกันมากจากการทำงานจากสิ่งที่วัตถุที่จะสร้าง
Neil

คำตอบ:


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
    }
});

และใช้งานได้จากทุกคลาสที่ลงทะเบียนกับ DI:

public class Consumer
{
    private readonly IService _aService;

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

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

โปรดจำไว้ว่าในตัวอย่างนี้กุญแจสำหรับการแก้ปัญหาคือสตริงเพื่อความเรียบง่ายและเนื่องจาก OP ขอให้กรณีนี้เป็นพิเศษ

แต่คุณสามารถใช้ความละเอียดแบบกำหนดเองใด ๆ เป็นคีย์ได้เนื่องจากคุณไม่ต้องการสวิตช์ตัวใหญ่และตัวเล็กที่หมุนโค้ดของคุณ ขึ้นอยู่กับขนาดแอพของคุณ


1
@MatthewStevenMonkan อัปเดตคำตอบของฉันด้วยตัวอย่าง
Miguel A. Arilla

2
การใช้รูปแบบโรงงานเช่นนี้เป็นวิธีที่ดีที่สุด ขอบคุณสำหรับการแบ่งปัน!
Sergey Akopov

2
+1 เนี้ยบและสะอาดมากเพราะเมื่อเราใช้ di-container อื่นเราจะต้องรวมแพ็คเกจของพวกเขาทุกครั้งที่เราต้องแก้ไขการพึ่งพาเช่น ILifetimeScope ใน AutoFac
Anupam Singh

1
@ AnupamSingh ในความคิดของฉันแอพพลิเคชั่นขนาดเล็กถึงขนาดกลางส่วนใหญ่ที่รันบน. NET Core ไม่ต้องการ DI Framework ใด ๆ เพียงแค่เพิ่มความซับซ้อนและการพึ่งพาที่ไม่พึงประสงค์ความงามและความเรียบง่ายของ DI ในตัวนั้นมากเกินพอ ยังสามารถขยายได้อย่างง่ายดาย
Miguel A. Arilla

7
คำอธิบายลงคะแนน - มันน่าสนใจมาก แต่ตอนนี้ฉันกำลังสร้างฐานรหัสจำนวนมากเพื่อลบเวทมนตร์ Func ทั้งหมดที่ใครบางคนทำเมื่อไม่กี่ปีที่ผ่านมา (ก่อนการปฏิวัติ MS DI) ปัญหาที่เกิดขึ้นคือมันเพิ่มความซับซ้อน สามารถทำให้ความคมชัด DI ที่ซับซ้อนยิ่งกว่านั้นได้ ตัวอย่างเช่นฉันทำงานกับตัวจัดการบริการ Windows มีโค้ดมากกว่า 1.6k บรรทัดที่ทำกับ Func และหลังจากทำตามวิธีที่แนะนำของ DI ฉันได้ลดขนาดลงเป็น 0.2k บรรทัด OK-บรรทัดของรหัสอะไรหมายถึง .. ยกเว้นง่ายต่อการอ่านและ resuse ตอนนี้ ...
Piotr กุลา

79

อีกตัวเลือกหนึ่งคือการใช้วิธีการขยายจากGetServicesMicrosoft.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 ซึ่งแสดงให้ฉันเห็นว่าคุณสามารถขอรายการบริการที่ต้องพึ่งพาบริการได้โดยการร้องขอIEnumerable<IService>
johnny 5

20
serviceProvider.GetServices <IService> () จะยกตัวอย่าง ServiceA, ServiceB และ ServiceC แต่ละรายการ คุณต้องการเรียกคอนสตรัคเตอร์ของบริการเดียวเท่านั้น - บริการที่คุณต้องการจริงๆ นี่เป็นปัญหาใหญ่หากการใช้งานไม่ใช่น้ำหนักเบาหรือคุณมีการนำไปใช้งานหลายอย่างของ IService (ตัวอย่างเช่นคุณมีการใช้งาน IRepository ที่สร้างขึ้นอัตโนมัติสำหรับแต่ละรุ่น)
Uros

6
ฉันเห็นด้วยกับ @Uros นี่ไม่ใช่ทางออกที่ดี ลองนึกภาพว่าจะเกิดอะไรขึ้นถ้าคุณลงทะเบียน 10 การใช้งานบริการและอินสแตนซ์ที่คุณต้องการคืออันสุดท้าย ในกรณีนี้ 9 อินสแตนซ์ถูกสร้างขึ้นจริงโดย DI ซึ่งไม่เคยใช้
thomai

4
แนวคิดที่ไม่ดี: อินสแตนซ์ที่ไม่ได้ใช้หลายรายการรูปแบบการต่อต้านของตัวระบุตำแหน่งบริการและการเชื่อมต่อโดยตรงกับการนำไปใช้งานจริง (typeof <ServiceA>)
Rico Suter

20

Microsoft.Extensions.DependencyInjectionมันไม่ได้รับการสนับสนุนโดย

แต่คุณสามารถเสียบในกลไกการฉีดพึ่งพาอื่นเช่นStructureMap ดูมันเป็นหน้าแรกและมันของโครงการ GitHub

มันไม่ยากเลย:

  1. เพิ่มการพึ่งพากับ StructureMap ใน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 ทำการฉีดอัตโนมัติ
mohrtan

BTW ฉันกำลังใช้ StructureMap.Micorosoft.DependencyInject 1.3.0
mohrtan

คุณส่งคืนคอนเทนเนอร์ใหม่ใน ConfigureServices หรือไม่
Gerardo Grignoli

ฉันส่งคืน IServiceProviderInstance ของคอนเทนเนอร์ใหม่ตามที่ระบุในขั้นตอนที่ 2 ด้านบน ฉันคัดลอกแล้วเปลี่ยนเฉพาะประเภทของฉันเท่านั้น นี่เป็นทางออกที่ดีและทำงานได้อย่างสมบูรณ์ ข้อเสียเปรียบเพียงอย่างเดียวคือฉันไม่สามารถใช้ภาชนะที่ฉีดแล้วและหันไปใช้ภาชนะคงที่ซึ่งฉันไม่ต้องการทำ
mohrtan

1
มันใช้งานได้สำหรับฉันขอบคุณ GerardoGrignoli @mohrtan โค้ดตัวอย่างอยู่ที่นี่หากคุณยังคงมองหามันอยู่ github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza

13

คุณถูกต้องตัวสร้างบรรจุภัณฑ์ ASP.NET Core ในนั้นไม่มีแนวคิดในการลงทะเบียนบริการหลายอย่างจากนั้นดึงข้อมูลเฉพาะตามที่คุณแนะนำโรงงานเป็นทางออกที่แท้จริงเพียงอย่างเดียวในกรณีนั้น

อีกวิธีหนึ่งคุณสามารถสลับไปที่คอนเทนเนอร์ของบุคคลที่สามเช่น Unity หรือ StructureMap ที่ให้โซลูชันที่คุณต้องการ (จัดทำเอกสารไว้ที่นี่: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- the-default-services-container )


13

ฉันประสบปัญหาเดียวกันและต้องการแบ่งปันวิธีแก้ไขและทำไม

ดังที่คุณกล่าวถึงมีสองปัญหา ครั้งแรก:

ใน Asp.Net Core ฉันจะลงทะเบียนบริการเหล่านี้ได้อย่างไรและแก้ไขที่รันไทม์โดยใช้คีย์บางตัว

แล้วเรามีทางเลือกอะไรบ้าง? คนแนะนำสอง:

  • ใช้โรงงานที่กำหนดเอง (เช่น_myFactory.GetServiceByKey(key))

  • ใช้เครื่องมือ DI อื่น (เช่น_unityContainer.Resolve<IService>(key))

รูปแบบจากโรงงานเป็นตัวเลือกเดียวที่นี่หรือไม่

ในความเป็นจริงทั้งสองตัวเลือกเป็นโรงงานเพราะแต่ละคอนเทนเนอร์ IoC ยังเป็นโรงงาน (กำหนดค่าได้สูงและซับซ้อนแม้ว่า) และสำหรับฉันแล้วดูเหมือนว่าตัวเลือกอื่น ๆ ก็มีรูปแบบของรูปแบบโรงงานเช่นกัน

แล้วตัวเลือกไหนดีกว่ากัน? ที่นี่ฉันเห็นด้วยกับ @Sock ผู้แนะนำให้ใช้โรงงานที่กำหนดเองและนั่นคือเหตุผล

ก่อนอื่นฉันพยายามหลีกเลี่ยงการเพิ่มการขึ้นต่อกันใหม่เมื่อไม่จำเป็นจริงๆ ดังนั้นฉันเห็นด้วยกับคุณในจุดนี้ ยิ่งกว่านั้นการใช้สองเฟรมเวิร์ก DI เลวร้ายยิ่งกว่าการสร้างนามธรรมจากโรงงานที่กำหนดเอง ในกรณีที่สองคุณต้องเพิ่มการพึ่งพาแพคเกจใหม่ (เช่น Unity) แต่ขึ้นอยู่กับอินเทอร์เฟซจากโรงงานใหม่คือความชั่วร้ายที่นี่น้อยลง ฉันเชื่อว่าแนวคิดหลักของ ASP.NET Core DI คือความเรียบง่าย มันจะเก็บชุดที่น้อยที่สุดของคุณสมบัติดังต่อไปหลักการ KISS หากคุณต้องการคุณสมบัติพิเศษจากนั้นทำ DIY หรือใช้Plungin ที่สอดคล้องกันที่ใช้คุณสมบัติที่ต้องการ (หลักการเปิดปิด)

ประการที่สองบ่อยครั้งที่เราต้องฉีดการอ้างอิงที่มีชื่อจำนวนมากสำหรับบริการเดียว ในกรณีของ Unity คุณอาจต้องระบุชื่อสำหรับพารามิเตอร์ Constructor (using InjectionConstructor) การลงทะเบียนนี้ใช้การไตร่ตรองและตรรกะที่ชาญฉลาดเพื่อคาดเดาอาร์กิวเมนต์สำหรับตัวสร้าง สิ่งนี้อาจนำไปสู่ข้อผิดพลาดรันไทม์หากการลงทะเบียนไม่ตรงกับอาร์กิวเมนต์ตัวสร้าง ในทางกลับกันเมื่อใช้โรงงานของคุณเองคุณสามารถควบคุมวิธีกำหนดพารามิเตอร์คอนสตรัคเตอร์ได้อย่างสมบูรณ์ สามารถอ่านได้มากขึ้นและแก้ไขได้ในเวลารวบรวม หลักการจูบอีกครั้ง

ปัญหาที่สอง:

_serviceProvider.GetService () สามารถฉีดสตริงการเชื่อมต่อที่เหมาะสมได้อย่างไร

ครั้งแรกฉันเห็นด้วยกับคุณว่าขึ้นอยู่กับสิ่งใหม่ ๆ เช่นIOptions(และดังนั้นในแพคเกจMicrosoft.Extensions.Options.ConfigurationExtensions) ไม่ใช่ความคิดที่ดี ฉันเคยเห็นบางคนพูดคุยเกี่ยวกับIOptionsสถานที่ที่มีความคิดเห็นที่แตกต่างกันเกี่ยวกับประโยชน์ของมัน อีกครั้งฉันพยายามหลีกเลี่ยงการเพิ่มการอ้างอิงใหม่เมื่อไม่จำเป็นจริงๆ มันจำเป็นจริงๆเหรอ? ฉันคิดว่าไม่ มิฉะนั้นการใช้งานแต่ละครั้งจะต้องขึ้นอยู่กับมันโดยไม่จำเป็นต้องมีความชัดเจนใด ๆ ที่มาจากการใช้งานนั้น (สำหรับฉันดูเหมือนว่ามีการละเมิด ISP ซึ่งฉันเห็นด้วยกับคุณด้วย) สิ่งนี้เป็นจริงเกี่ยวกับขึ้นอยู่กับโรงงานด้วย แต่ในกรณีนี้สามารถหลีกเลี่ยงได้

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 ส่วนขยายการอ้างอิงการอ้างอิง ... คุณคิดว่าสร้างอินเทอร์เฟซ 3 รายการที่ขยาย Iservice เช่น "ส่วนต่อประสานสาธารณะสาธารณะ IServiceA: IService" และมากกว่า "ชั้นสาธารณะ ServiceA: IServiceA" ... อาจเป็นตัวเลือกในการฝึกฝนที่ดี?
Emiliano Magliocca

1
@ emiliano-magliocca โดยทั่วไปคุณไม่ควรขึ้นอยู่กับส่วนต่อประสานที่คุณไม่ได้ใช้ (ISP) IServiceAในกรณีของคุณ เนื่องจากคุณใช้วิธีการจากIServiceเท่านั้นคุณควรพึ่งIServiceเท่านั้น
neleus

1
@ cagatay-kalan ในกรณีที่คำถามของ OP เขาสามารถบรรลุเป้าหมายได้อย่างง่ายดายด้วย ASP.NET Core DI ไม่ต้องการกรอบ DI อื่น ๆ
neleus

1
@EmilianoMagliocca สามารถแก้ไขได้อย่างง่ายดายด้วยวิธีนี้: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));สำหรับชั้นหนึ่งและชั้นservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));ที่สอง
neleus

1
@EmilianoMagliocca ในตัวอย่างของฉันทั้ง 'MyFirstClass' และ 'MySecondClass' มีพารามิเตอร์ ctor ที่เหมือนกันของประเภทอินเตอร์เฟสซึ่งทั้ง Escpos และ Usbpos ใช้ ดังนั้นโค้ดด้านบนจะแนะนำเฉพาะคอนเทนเนอร์ IoC วิธีการติดตั้ง 'MyFirstClass' และ 'MySecondClass' ไม่มีอะไรเพิ่มเติม ดังนั้นนอกจากนี้คุณอาจต้องแมปอินเทอร์เฟซอื่น ๆ กับ 'MyFirstClass' และ 'MySecondClass' ขึ้นอยู่กับความต้องการของคุณและฉันไม่ได้กล่าวถึงในตัวอย่าง
neleus

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 () ของคอนโทรลเลอร์คุณสามารถใช้ typeof เพื่อแก้ไขบริการที่คุณต้องการ: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen

แท้จริงฉันลองทุกอย่างและนี่เป็นทางออกเดียวที่เหมาะกับฉัน ขอบคุณ!
Skatz1990

@ Skatz1990 ลองวิธีการแก้ปัญหาที่ฉันสร้างด้านล่างในโพสต์อื่น ฉันคิดว่ามันสะอาดและใช้ง่ายกว่า
T Brown

12

คำตอบส่วนใหญ่ที่นี่ละเมิดหลักการความรับผิดชอบเดียว (คลาสบริการไม่ควรแก้ไขการพึ่งพาตัวเอง) และ / หรือใช้รูปแบบการต่อต้านบริการระบุตำแหน่ง

ตัวเลือกอื่นเพื่อหลีกเลี่ยงปัญหาเหล่านี้คือ:

  • ใช้พารามิเตอร์ประเภททั่วไปเพิ่มเติมบนอินเทอร์เฟซหรืออินเทอร์เฟซใหม่ที่ใช้อินเทอร์เฟซที่ไม่ใช่แบบทั่วไป
  • ใช้คลาสอะแดปเตอร์ / interceptor เพื่อเพิ่มประเภทเครื่องหมายแล้ว
  • ใช้ประเภททั่วไปเป็น "ชื่อ"

ฉันได้เขียนบทความที่มีรายละเอียดเพิ่มเติม: การพึ่งพาการฉีดใน. NET: วิธีแก้ไขการลงทะเบียนที่ไม่มีชื่อ


คำตอบที่ได้รับการยอมรับ violets หลักการความรับผิดชอบเดียว?
LP13

ดูความคิดเห็นของstackoverflow.com/a/52066039/876814และในคำตอบที่ได้รับการยอมรับบริการแก้ไขอย่างขี้เกียจนั่นคือคุณรู้ว่ามันล้มเหลวที่ runtime และไม่มีวิธีที่จะตรวจสอบสิ่งนี้เมื่อเริ่มต้นหลังจากสร้างคอนเทนเนอร์ (คล้ายกับ คำตอบในความคิดเห็น) SRP เนื่องจากบริการไม่เพียง แต่รับผิดชอบต่อตรรกะทางธุรกิจเท่านั้น แต่ยังรวมถึงการแก้ปัญหาการพึ่งพา
Rico Suter

@ RicoSuter ฉันชอบโซลูชันในบล็อกของคุณ แต่ฉันสับสนโดย DI ของคุณในคลาสเริ่มต้น โดยเฉพาะฉันไม่เข้าใจบรรทัด MessagePublisher ("MyOrderCreatedQueue") เนื่องจากฉันไม่เห็นตัวสร้างที่มีลายเซ็นนั้น services.AddSingleton <IMessagePublisher <OrderCreatedMessage>> (ข้อความใหม่ผู้เผยแพร่ <OrderCreatedMessage> (ข้อความใหม่ผู้เผยแพร่ ("MyOrderCreatedQueue"));
Lee Z

ขอบคุณปรับปรุงบทความและใช้ MyMessagePublisher เป็นตัวอย่างการใช้งานของ IMessagePublisher
Rico Suter

7

สายไปงานปาร์ตี้นี้ แต่นี่คือทางออกของฉัน: ...

Startup.cs หรือ Program.cs ถ้า Generic Handler ...

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

IMyInterface ของการตั้งค่า T Interface

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

การประยุกต์ใช้ IMyInterface ของ T

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

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

หวังว่าหากมีปัญหาเกี่ยวกับการทำเช่นนี้ใครบางคนจะกรุณาชี้ให้เห็นว่าทำไมนี้เป็นวิธีที่ผิดในการทำเช่นนี้


2
IMyInterface<CustomerSavedConsumer>และIMyInterface<ManagerSavedConsumer>เป็นประเภทบริการที่แตกต่างกัน - นี่ไม่ได้ตอบคำถาม OPs เลย
Richard Hauer

2
OP ต้องการวิธีการลงทะเบียนการใช้งานหลาย ๆ อินเทอร์เฟซเดียวกันในแกน Asp.net หากฉันไม่ได้ทำสิ่งนี้โปรดอธิบายวิธี (อย่างแน่นอน)
สีเทา

1
ในขณะที่คุณถูกต้องรูปแบบนี้อนุญาตให้เอฟเฟกต์ที่ op ต้องการ อย่างน้อยตอนที่ฉันพยายามทำสิ่งนี้เองฉันก็สะดุดกับโพสต์นี้และทางออกของฉันก็ทำงานได้ดีที่สุดสำหรับสถานการณ์ของฉัน
สีเทา

1
ฉันคาดหวังว่าปัญหาจะยิ่งกว่านั้นการลงทะเบียนการใช้งานหลายอย่างสำหรับอินเทอร์เฟซเดียว (โดยใช้ MS DI) ไม่อนุญาตให้คอนเทนเนอร์แยกความแตกต่างของการใช้งานหนึ่งจากอีกอันหนึ่ง ใน DIs อื่น ๆ คุณสามารถป้อนมันเพื่อให้คอนเทนเนอร์รู้ว่าควรเลือกแบบใด ใน MS คุณต้องใช้ผู้รับมอบสิทธิ์และเลือกด้วยตนเอง โซลูชันของคุณไม่ได้ระบุสถานการณ์นี้เนื่องจากอินเทอร์เฟซของคุณแตกต่างกันดังนั้นคอนเทนเนอร์จึงไม่มีปัญหาในการเลือกการใช้งานที่ถูกต้อง ในขณะที่ตัวอย่างของคุณใช้งานได้จริงมันไม่ใช่วิธีแก้ปัญหาตามที่ระบุไว้
Richard Hauer

3
@Gray ถึงแม้ว่าการโพสต์ของคุณจะมีข่าวไม่ดี แต่ฉันขอขอบคุณที่นำวิธีนี้ไปใช้ มันทำให้ผู้อ่านมีทางเลือกอีกทางหนึ่งในการเอาชนะข้อ จำกัด ใน. net cores DI แม้ว่ามันอาจจะไม่ตอบคำถาม OPs โดยตรง แต่ก็มีทางเลือกที่สมบูรณ์แบบซึ่งเป็นเรื่องเกี่ยวกับ SO ใช่มั้ย?
Neil Watson

5

เห็นได้ชัดว่าคุณสามารถฉีด IEnumerable อินเตอร์เฟสบริการของคุณได้! จากนั้นค้นหาอินสแตนซ์ที่คุณต้องการใช้ LINQ

ตัวอย่างของฉันสำหรับบริการ AWS SNS แต่คุณสามารถทำเช่นเดียวกันกับบริการที่ฉีดเข้าไปจริงๆ

การเริ่มต้น

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",

โรงงาน SNS

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();
}

ตอนนี้คุณสามารถรับบริการ SNS สำหรับภูมิภาคที่คุณต้องการในบริการหรือคอนโทรลเลอร์แบบกำหนดเองของคุณ

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"));

จากนั้นลงทะเบียนพจนานุกรมกับ Service-collection:

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;
            };
    });

แต่นั่นต้องเปลี่ยนอินเทอร์เฟซของคุณเพื่อรองรับคุณสมบัติและการวนซ้ำผ่านองค์ประกอบจำนวนมากควรช้ากว่าการค้นหาแบบเชื่อมโยง (พจนานุกรม)
เป็นเรื่องดีที่ได้รู้ว่าสามารถทำได้โดยไม่ต้องมี dicionary

นี่เป็นเพียง $ 0.05 ของฉัน


หากบริการได้IDisposeดำเนินการแล้วใครเป็นผู้รับผิดชอบในการกำจัดบริการ? คุณได้ลงทะเบียนพจนานุกรมว่าSingleton
LP13

@ LP13: คุณสามารถลงทะเบียนพจนานุกรมด้วยผู้รับมอบสิทธิ์เป็นค่าจากนั้นคุณสามารถลงทะเบียนใน itransient และสร้างอินสแตนซ์ใหม่เช่น GetRequiredService <T> () ["logDB"] ()
Stefan Steiger

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;
        }
    }
}

คุณสามารถระบุวิธีการเพิ่มเติม AddFactory () ได้หรือไม่
นักพัฒนา

ขออภัยเพิ่งเห็นสิ่งนี้ ... เพิ่ม
T Brown

3

ในขณะที่ดูเหมือน @Miguel A. Arilla ได้ชี้ให้เห็นอย่างชัดเจนและฉันโหวตให้เขา แต่ฉันได้สร้างโซลูชันที่มีประโยชน์ของเขาให้กับโซลูชันอื่นซึ่งดูเรียบร้อย แต่ต้องใช้งานมากขึ้น

แน่นอนขึ้นอยู่กับวิธีการแก้ปัญหาข้างต้น ดังนั้นโดยพื้นฐานแล้วฉันสร้างบางสิ่งที่คล้ายกับ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;
        }

Accessor บริการดูเหมือนว่า:

 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");

เพียงพอแล้วในตอนนี้ แต่เพื่อให้งานของคุณเสร็จสมบูรณ์คุณควรเพิ่มวิธีการขยายเพิ่มเติมให้มากขึ้นเพื่อครอบคลุมการลงทะเบียนทุกประเภทโดยใช้วิธีการเดียวกัน

มีอีกโพสต์เกี่ยวกับ stackoverflow แต่ฉันไม่สามารถหาได้ที่โปสเตอร์ได้อธิบายรายละเอียดว่าทำไมคุณสมบัตินี้จึงไม่รองรับและวิธีการแก้ไขโดยทั่วไปคล้ายกับที่ @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
สิ่งนี้ช่วยแก้ปัญหาของฉันเมื่อฉันสูญเสียการลงทะเบียนประเภทใน accessor บริการ เคล็ดลับคือการลบการเชื่อมโยงทั้งหมดสำหรับ accessor บริการแล้วเพิ่มอีกครั้ง!
Umar Farooq Khawaja

3

ฉันได้สร้างห้องสมุดสำหรับสิ่งนี้ซึ่งใช้คุณสมบัติที่ดีบางอย่าง รหัสสามารถพบได้ใน GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInject/

การใช้งานตรงไปตรงมา:

  1. เพิ่ม Dazinator.Extensions.DependencyInject package 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.

    });

ในตัวอย่างข้างต้นโปรดสังเกตว่าสำหรับการลงทะเบียนแต่ละครั้งคุณจะระบุอายุการใช้งานหรือ Singleton, Scoped หรือ Transient

คุณสามารถแก้ไขบริการได้สองวิธีโดยขึ้นอยู่กับว่าคุณพอใจกับการให้บริการของคุณหรือไม่ขึ้นกับแพ็คเกจนี้:

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. เมื่อคุณลงทะเบียนชื่อบริการประเภทใด ๆ ที่คุณลงทะเบียนสามารถมีการก่อสร้างที่มีพารามิเตอร์ - พวกเขาจะได้รับความพึงพอใจผ่าน DI ในทางเดียวกันกับที่AddTransient<>, AddScoped<>และAddSingleton<>วิธีการทำงานตามปกติ

  2. สำหรับบริการที่กำหนดชื่อแบบชั่วคราวและแบบกำหนดขอบเขตรีจีสทรีจะสร้างObjectFactoryเพื่อให้สามารถเปิดใช้งานอินสแตนซ์ใหม่ของประเภทได้อย่างรวดเร็วเมื่อต้องการ นี่เป็นวิธีที่เร็วกว่าวิธีอื่นและสอดคล้องกับวิธีการของ Microsoft ส่วนขยายการอ้างอิงการทำสิ่งต่างๆ


2

โซลูชันของฉันสำหรับสิ่งที่คุ้มค่า ... ลองเปลี่ยนมาใช้ Castle Windsor เพราะไม่สามารถพูดได้ว่าฉันชอบโซลูชันใด ๆ ข้างต้น ขออภัย !!

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) บริการที่ @Craig Brunetti แนะนำบริการ

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();
    }
}

เอาชนะวัตถุประสงค์ของ IoC คุณอาจจะเขียน:var serviceA = new ServiceA();
James Curran

2
@JamesCurran ไม่ใช่ถ้า ServiceA มีการขึ้นต่อกันหรือถ้าคุณต้องการทดสอบหน่วย
Jorn.Beyers

0

ในขณะที่การใช้งานนอกกรอบไม่ได้นำเสนอนี่เป็นตัวอย่างโครงการที่ให้คุณลงทะเบียนอินสแตนซ์ที่มีชื่อแล้วฉีด INamedServiceFactory ลงในรหัสของคุณและดึงอินสแตนซ์ออกจากชื่อ แตกต่างจากโซลูชัน facory อื่น ๆ ที่นี่จะช่วยให้คุณสามารถลงทะเบียนอินสแตนซ์หลายตัวของการใช้งานเดียวกันแต่กำหนดค่าแตกต่างกัน

https://github.com/macsux/DotNetDINamedInstances


0

บริการเกี่ยวกับบริการเป็นอย่างไร

ถ้าเรามีอินเทอร์เฟซ INamedService (พร้อมด้วย. ชื่อคุณสมบัติ) เราสามารถเขียนส่วนขยาย 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 แต่คุณจะได้รับการแก้ปัญหาตามคีย์ที่คุณต้องการใช่ไหม

เพื่อความเป็นธรรมการมีอินเทอร์เฟซ INamedService นี้ดูน่าเกลียด แต่ถ้าคุณต้องการที่จะไปต่อและทำให้สิ่งต่าง ๆ ดูดียิ่งขึ้นดังนั้น [NamedServiceAttribute ("A")] ในการใช้งาน / คลาสสามารถพบได้โดยรหัสในนี้ ส่วนขยายและมันก็ใช้ได้เช่นกัน เพื่อให้มีความยุติธรรมมากยิ่งขึ้น Reflection จึงช้าดังนั้นการปรับให้เหมาะสมอาจเป็นไปตามลำดับ แต่โดยสุจริตนั่นเป็นสิ่งที่เอ็นจิ้น DI ควรช่วยด้วย ความรวดเร็วและความเรียบง่ายเป็นผลงานที่ยิ่งใหญ่ของ TCO

สรุปแล้วไม่จำเป็นต้องมีโรงงานที่ชัดเจนเพราะ "การค้นหาบริการที่มีชื่อ" นั้นเป็นแนวคิดที่สามารถนำกลับมาใช้ใหม่ได้และคลาสโรงงานไม่ได้ปรับขนาดเป็นโซลูชัน และ Func <> นั้นดูดี แต่บล็อกสวิตช์นั้นเยือกเย็นและอีกครั้งคุณจะเขียน Funcs บ่อยเท่าที่คุณกำลังเขียนโรงงาน เริ่มต้นง่าย ๆ ใช้ซ้ำได้โดยใช้โค้ดน้อยลงและถ้านั่นไม่ใช่ว่าจะทำเพื่อคุณ


2
รูปแบบนี้เรียกว่า service locator pattern และโดยทั่วไปไม่ใช่เส้นทางที่ดีที่สุดที่จะไปเว้นแต่คุณจะต้อง
Joe Phillips

@JoePhillips คุณมีความเห็นว่าทำไมมันจึงไม่ใช่ทางออกที่ดีใช่ไหม ฉันรักความสง่างามของมัน ข้อเสียเดียวที่ฉันคิดได้ก็คือฉันสร้างตัวอย่างของพวกเขาทุกครั้งที่คุณได้รับ
ปีเตอร์

2
@ Peter เหตุผลหลักก็คือมันยากมากที่จะทำงานด้วย หากคุณกำลังส่งผ่านวัตถุ serviceLocator ไปยังคลาสมันไม่ชัดเจนเลยว่าการพึ่งพาคลาสใดที่ใช้เนื่องจากมันทำให้พวกเขาทั้งหมดมาจากวัตถุ "เทพเจ้า" เวทมนตร์ ลองนึกภาพว่าต้องค้นหาการอ้างอิงประเภทที่คุณต้องการเปลี่ยน โดยทั่วไปความสามารถนั้นจะหายไปเมื่อคุณได้รับทุกอย่างผ่านออบเจ็กต์ตัวระบุบริการ การสร้างคอนสตรัคเตอร์นั้นชัดเจนและน่าเชื่อถือมากขึ้น
Joe Phillips

ฉันไม่รู้. ความชัดเจนไม่ได้เป็นลบสำหรับฉัน ... เพราะถ้าฉันใส่ใจเกี่ยวกับการติดตามว่าองค์ประกอบของฉันใช้ประโยชน์จากการพึ่งพาได้อย่างไรฉันจะมีการทดสอบหน่วยสำหรับการทดสอบนั้น ... การทดสอบที่ไม่เพียง แต่อ้างอิงการพึ่งพาแต่ละครั้ง แต่ช่วยให้เราเข้าใจ แต่ละการพึ่งพาเป็นสิ่งจำเป็น คุณจะรู้เรื่องอื่นอย่างไรโดยการอ่านคอนสตรัคเตอร์?!?
Craig Brunetti

0

ฉันพบปัญหาเดียวกันและทำงานกับส่วนขยายอย่างง่ายเพื่อให้บริการ Named คุณสามารถค้นหาได้ที่นี่:

ช่วยให้คุณเพิ่มบริการ (ชื่อ) ได้มากเท่าที่คุณต้องการเช่นนี้:

 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);

โปรดจำไว้ว่า serviceProvider สามารถฉีดเข้าไปภายในตัวสร้างในแอปพลิเคชันของเราเป็น IServiceProviderที่สามารถฉีดภายในคอนสตรัคในโปรแกรมของเราเป็น

ฉันหวังว่านี่จะช่วยได้.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.