Làm cách nào để đăng ký nhiều triển khai của cùng một giao diện trong Asp.Net Core?


239

Tôi có các dịch vụ có nguồn gốc từ cùng một giao diện.

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

Thông thường, các bộ chứa IoC khác như Unitycho phép bạn đăng ký triển khai cụ thể bằng một số cách Keyphân biệt chúng.

Trong ASP.NET Core, làm cách nào để tôi đăng ký các dịch vụ này và giải quyết chúng khi chạy dựa trên một số khóa?

Tôi không thấy bất kỳ Addphương thức Dịch vụ nào có tham số keyhoặc nametham số, thường được sử dụng để phân biệt việc triển khai cụ thể.

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

Có phải mẫu Factory là lựa chọn duy nhất ở đây?

Update1
Tôi đã đi qua bài viết ở đây chỉ ra cách sử dụng mẫu nhà máy để lấy các phiên bản dịch vụ khi chúng ta có nhiều triển khai cụ thể. Tuy nhiên, nó vẫn chưa phải là một giải pháp hoàn chỉnh. Khi tôi gọi _serviceProvider.GetService()phương thức, tôi không thể tiêm dữ liệu vào hàm tạo.

Ví dụ, hãy xem xét điều này:

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

Làm thế nào có thể _serviceProvider.GetService()tiêm chuỗi kết nối thích hợp? Trong Unity hoặc bất kỳ thư viện IoC nào khác, chúng tôi có thể làm điều đó khi đăng ký loại. Tôi có thể sử dụng IOption , tuy nhiên, điều đó sẽ yêu cầu tôi tiêm tất cả các cài đặt. Tôi không thể tiêm một chuỗi kết nối cụ thể vào dịch vụ.

Cũng lưu ý rằng tôi đang cố gắng tránh sử dụng các container khác (bao gồm cả Unity) vì sau đó tôi phải đăng ký mọi thứ khác (ví dụ: Bộ điều khiển) với container mới.

Ngoài ra, sử dụng mẫu nhà máy để tạo các phiên bản dịch vụ là chống lại DIP, vì nó làm tăng số lượng phụ thuộc mà khách hàng có chi tiết ở đây .

Vì vậy, tôi nghĩ rằng DI mặc định trong ASP.NET Core thiếu hai điều:

  1. Khả năng đăng ký cá thể bằng khóa
  2. Khả năng tiêm dữ liệu tĩnh vào các nhà xây dựng trong quá trình đăng ký


2
Cuối cùng cũng có một phần mở rộng trong nuget cho đăng ký dựa trên tên, hy vọng nó có thể giúp
neleus

Xin chào, xin lỗi vì câu hỏi ngu ngốc của tôi, nhưng tôi mới biết về Microsoft.Extensions.DependencyInjection ... bạn có nghĩ rằng tạo 3 giao diện trống mở rộng Iservice như "giao diện công cộng IServiceA: IService" và hơn "lớp dịch vụ công cộngA: IServiceA "... Có thể là một lựa chọn thực hành tốt?
Emiliano Magliocca

bài viết này có ích gì không? stevejgordon.co.uk/ từ
Mike B

Có thể Update1được chuyển sang một câu hỏi khác vì việc đưa mọi thứ vào các nhà xây dựng rất khác với việc tìm ra đối tượng nào sẽ xây dựng
Neil

Câu trả lời:


246

Tôi đã làm một cách giải quyết đơn giản bằng cách sử dụng Func khi tôi thấy mình trong tình huống này.

Đầu tiên tuyên bố một đại biểu được chia sẻ:

public delegate IService ServiceResolver(string key);

Sau đó, trong Startup.csthiết lập của bạn , thiết lập nhiều đăng ký cụ thể và ánh xạ thủ công của các loại đó:

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

Và sử dụng nó từ bất kỳ lớp nào đã đăng ký với DI:

public class Consumer
{
    private readonly IService _aService;

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

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

Hãy nhớ rằng trong ví dụ này, khóa để giải quyết là một chuỗi, vì đơn giản và vì OP đã yêu cầu trường hợp này nói riêng.

Nhưng bạn có thể sử dụng bất kỳ loại độ phân giải tùy chỉnh nào làm khóa, vì bạn thường không muốn một công tắc trường hợp n lớn làm mục tiêu mã của bạn. Phụ thuộc vào cách ứng dụng của bạn quy mô.


1
@MatthewStevenMonkan đã cập nhật câu trả lời của tôi bằng một ví dụ
Miguel A. Arilla

2
Sử dụng một mô hình nhà máy như thế này là cách tốt nhất để đi. Cám ơn vì đã chia sẻ!
Serge Akopov

2
+1 Rất gọn gàng và sạch sẽ, bởi vì khi chúng tôi sử dụng các di-container khác, chúng tôi phải bao gồm gói của họ bất cứ khi nào chúng tôi cần giải quyết các phụ thuộc, ví dụ. ILifetimeScope trong AutoFac.
Anupam Singh

1
@AnupamSingh Theo tôi, hầu hết các loại ứng dụng vừa và nhỏ chạy trên .NET Core không cần bất kỳ khung DI nào, chỉ cần thêm sự phức tạp và phụ thuộc không mong muốn, vẻ đẹp và sự đơn giản của DI tích hợp là quá đủ, và nó có thể cũng được mở rộng một cách dễ dàng.
Miguel A. Arilla

7
Giải thích bỏ phiếu - Rất thú vị nhưng tôi hiện đang tái cấu trúc một cơ sở mã lớn để loại bỏ tất cả ma thuật Func này mà ai đó đã làm cách đây vài năm (trước cuộc cách mạng MS DI) Vấn đề với điều này là nó làm tăng đáng kể độ phức tạp của tính hấp dẫn đối với các thuộc tính. có thể gây ra độ phân giải DI phức tạp hơn nữa xuống dòng. Ví dụ, tôi đã làm việc với một trình xử lý dịch vụ Windows có hơn 1,6 nghìn dòng mã để làm với Func và sau khi thực hiện theo cách DI được đề xuất, tôi đã giảm nó xuống còn 0,2k dòng. OK - Các dòng mã không có nghĩa gì .. ngoại trừ việc đọc và sử dụng lại dễ dàng hơn bây giờ ...
Piotr Kula

79

Một lựa chọn khác là sử dụng phương thức mở rộng GetServicestừ Microsoft.Extensions.DependencyInjection.

Đăng ký dịch vụ của bạn như:

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

Sau đó giải quyết với một chút Linq:

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

hoặc là

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

(giả sử rằng IService có một thuộc tính chuỗi được gọi là "Tên")

Đảm bảo có using Microsoft.Extensions.DependencyInjection;

Cập nhật

Nguồn AspNet 2.1: GetServices


6
Không chắc chắn, nhưng tôi nghĩ nó không mang tính quyết định. Bất kỳ kết quả bạn nhận được hôm nay có thể thay đổi vào ngày mai, có vẻ như không phải là một thực hành tốt.
rnrneverdies

4
upvote vào liên kết cho GetService, cho tôi thấy rằng bạn có thể yêu cầu danh sách các dịch vụ một dịch vụ phụ thuộc bằng cách yêu cầuIEnumerable<IService>
johnny 5

20
serviceProvider.GetService <IService> () sẽ khởi tạo từng ServiceA, ServiceB và ServiceC. Bạn muốn gọi hàm tạo của chỉ một dịch vụ - dịch vụ mà bạn thực sự cần. Đây là một vấn đề lớn nếu việc triển khai không có trọng lượng nhẹ hoặc bạn có nhiều triển khai IService (ví dụ: bạn đã tự động tạo các triển khai IRep repository cho mỗi mô hình).
Uros

6
Tôi đồng ý với @Uros. Đây không phải là một giải pháp tốt. Hãy tưởng tượng điều gì xảy ra nếu bạn đăng ký 10 triển khai IService và trường hợp bạn thực sự cần là lần cuối cùng. Trong trường hợp này, 9 trường hợp thực sự được tạo bởi DI, không bao giờ được sử dụng.
thomai

4
Ý tưởng tồi: Nhiều trường hợp không được sử dụng, mô hình chống định vị dịch vụ và khớp nối trực tiếp với việc triển khai thực tế (typeof <ServiceA>).
Rico Suter

20

Nó không được hỗ trợ bởi Microsoft.Extensions.DependencyInjection .

Nhưng bạn có thể cắm vào một cơ chế tiêm phụ thuộc khác, như StructureMap Xem Trang chủDự án GitHub .

Nó không khó chút nào:

  1. Thêm một phụ thuộc vào StructMap trong project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. Tiêm nó vào đường dẫn ASP.NET bên trong ConfigureServicesvà đăng ký các lớp của bạn (xem tài liệu)

    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. Sau đó, để có được một thể hiện được đặt tên, bạn sẽ cần phải yêu cầu IContainer

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

Đó là nó.

Ví dụ để xây dựng, bạn cần

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

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

        public string Name {get; set; }
    }

Tôi đã thử phương pháp này, nhưng tôi gặp lỗi thời gian chạy trên bộ điều khiển của mình vì IContainer không được tìm thấy trong các kế hoạch xây dựng. Có bất cứ điều gì tôi phải yêu cầu IContainer được tự động tiêm không?
mohrtan

BTW, tôi đang sử dụng StructMap.Micorosoft.DependencyInjection 1.3.0.
mohrtan

Bạn có trả lại container mới trong ConfigureService không?
Gerardo Grignoli

Tôi sẽ trả lại IServiceProviderInstance của container mới như được chỉ ra trong bước # 2 ở trên. Tôi đã sao chép chính xác chỉ thay đổi nó cho các loại của tôi. Đây là một giải pháp tốt và đang hoạt động hoàn hảo. Hạn chế duy nhất là tôi không thể sử dụng một container được tiêm và đang dùng đến một container tĩnh, điều mà tôi không muốn làm.
mohrtan 17/03/2017

1
Nó hoạt động với tôi nhờ GerardoGrignoli. @mohrtan mã mẫu ở đây nếu bạn vẫn đang xem xét điều này. github.com/Yawarmurtaza/AspNetCoreStr structMap
Yawar Murtaza

13

Nói đúng, bộ chứa ASP.NET Core tích hợp không có khái niệm đăng ký nhiều dịch vụ và sau đó lấy một dịch vụ cụ thể, như bạn đề xuất, một nhà máy là giải pháp thực sự duy nhất trong trường hợp đó.

Ngoài ra, bạn có thể chuyển sang bộ chứa bên thứ ba như Unity hoặc StructMap cung cấp giải pháp bạn cần (tài liệu ở đây: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replaces- the-default-services-container ).


13

Tôi đã đối mặt với cùng một vấn đề và muốn chia sẻ cách tôi giải quyết nó và tại sao.

Như bạn đã đề cập có hai vấn đề. Thứ nhất:

Trong Asp.Net Core, làm cách nào để tôi đăng ký các dịch vụ này và giải quyết nó trong thời gian chạy dựa trên một số khóa?

Vậy chúng ta có những lựa chọn nào? Mọi người đề nghị hai:

  • Sử dụng một nhà máy tùy chỉnh (như _myFactory.GetServiceByKey(key))

  • Sử dụng một công cụ DI khác (như _unityContainer.Resolve<IService>(key))

Có phải mẫu Factory là lựa chọn duy nhất ở đây?

Trên thực tế, cả hai tùy chọn đều là nhà máy vì mỗi IoC Container cũng là một nhà máy (mặc dù cấu hình cao và phức tạp). Và dường như các tùy chọn khác cũng là các biến thể của mẫu Factory.

Vậy lựa chọn nào tốt hơn? Ở đây tôi đồng ý với @Sock, người đã đề xuất sử dụng nhà máy tùy chỉnh và đó là lý do.

Đầu tiên, tôi luôn cố gắng tránh thêm các phụ thuộc mới khi chúng không thực sự cần thiết. Vì vậy, tôi đồng ý với bạn trong điểm này. Hơn nữa, sử dụng hai khung DI còn tệ hơn việc tạo ra sự trừu tượng hóa tùy chỉnh của nhà máy. Trong trường hợp thứ hai, bạn phải thêm phụ thuộc gói mới (như Unity) nhưng tùy thuộc vào giao diện nhà máy mới thì ít tệ hơn ở đây. Ý tưởng chính của ASP.NET Core DI, tôi tin, là sự đơn giản. Nó duy trì một bộ tính năng tối thiểu theo nguyên tắc KISS . Nếu bạn cần một số tính năng bổ sung thì DIY hoặc sử dụng Plungin tương ứng thực hiện tính năng mong muốn (Nguyên tắc đóng mở).

Thứ hai, thường chúng ta cần tiêm nhiều phụ thuộc có tên cho một dịch vụ. Trong trường hợp Unity, bạn có thể phải chỉ định tên cho tham số hàm tạo (sử dụng InjectionConstructor). Đăng ký này sử dụng sự phản chiếu và một số logic thông minh để đoán các đối số cho hàm tạo. Điều này cũng có thể dẫn đến lỗi thời gian chạy nếu đăng ký không khớp với các đối số của hàm tạo. Mặt khác, khi sử dụng nhà máy của riêng bạn, bạn có toàn quyền kiểm soát cách cung cấp các tham số của hàm tạo. Nó dễ đọc hơn và nó được giải quyết vào thời gian biên dịch. Nguyên tắc KISS một lần nữa.

Vấn đề thứ hai:

Làm thế nào _serviceProvider.GetService () tiêm chuỗi kết nối phù hợp?

Đầu tiên, tôi đồng ý với bạn rằng tùy thuộc vào những thứ mới như IOptions(và do đó trên gói Microsoft.Extensions.Options.ConfigurationExtensions) không phải là một ý tưởng tốt. Tôi đã thấy một số thảo luận về IOptionsnơi có ý kiến ​​khác nhau về lợi ích của nó. Một lần nữa, tôi cố gắng tránh thêm các phụ thuộc mới khi chúng không thực sự cần thiết. Có thực sự cần thiết? Tôi nghĩ rằng không. Nếu không, mỗi triển khai sẽ phải phụ thuộc vào nó mà không có nhu cầu rõ ràng đến từ việc triển khai đó (đối với tôi có vẻ như vi phạm ISP, nơi tôi cũng đồng ý với bạn). Điều này cũng đúng về tùy thuộc vào nhà máy nhưng trong trường hợp này có thể thể tránh được.

ASP.NET Core DI cung cấp một quá tải rất tốt cho mục đích đó:

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

Xin chào, xin lỗi vì câu hỏi ngu ngốc của tôi, nhưng tôi mới biết về Microsoft.Extensions.DependencyInjection ... bạn có nghĩ rằng tạo 3 giao diện mở rộng Iservice như "giao diện công cộng IServiceA: IService" và hơn "lớp dịch vụ công cộngA: IServiceA" ... Có thể là một lựa chọn thực hành tốt?
Emiliano Magliocca

1
@ emiliano-magliocca Nói chung, bạn không nên phụ thuộc vào các giao diện mà bạn không sử dụng (ISP), IServiceAtrong trường hợp của bạn. Vì bạn chỉ sử dụng các phương thức IService, nên bạn chỉ nên phụ thuộc IService.
neleus

1
@ cagatay-kalan Trong trường hợp câu hỏi của OP, anh ấy có thể dễ dàng đạt được mục tiêu của mình với ASP.NET Core DI. Không cần các khung DI khác.
neleus

1
@EmilianoMagliocca Có thể dễ dàng giải quyết theo cách này: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));cho lớp đầu tiên và services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));cho lớp thứ hai.
neleus

1
@EmilianoMagliocca trong ví dụ của tôi cả 'MyFirstClass' và 'MySecondClass' có cùng tham số ctor của loại giao diện mà cả Escpose và USBpose đều thực hiện. Vì vậy, mã ở trên chỉ hướng dẫn bộ chứa IoC cách truyền tải 'MyFirstClass' và 'MySecondClass'. Chỉ có bấy nhiêu thôi. Vì vậy, ngoài ra bạn có thể cần ánh xạ một số giao diện khác thành 'MyFirstClass' và 'MySecondClass'. Nó phụ thuộc vào nhu cầu của bạn và tôi đã không đề cập đến nó trong ví dụ của mình.
neleus

13

Tôi chỉ đơn giản là tiêm một IEnumerable

Cấu hình dịch vụ trong Startup.cs

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

Thư mục dịch vụ

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

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

Phần mở rộng.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;
    }

Trong phương thức DoS Something () của Trình điều khiển, bạn có thể sử dụng typeof để giải quyết dịch vụ bạn muốn: var service = _service.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen

Tôi thực sự đã thử mọi thứ, và đây là giải pháp duy nhất phù hợp với tôi. Cảm ơn!
Skatz1990

@ Skatz1990 Hãy thử giải pháp tôi đã tạo dưới đây trong một bài viết khác. Tôi nghĩ rằng nó là sạch hơn và đơn giản hơn để sử dụng.
T Brown

12

Hầu hết các câu trả lời ở đây đều vi phạm nguyên tắc trách nhiệm duy nhất (một lớp dịch vụ không nên tự giải quyết các phụ thuộc) và / hoặc sử dụng mô hình chống định vị dịch vụ.

Một lựa chọn khác để tránh những vấn đề này là:

  • sử dụng một tham số loại chung bổ sung trên giao diện hoặc giao diện mới thực hiện giao diện không chung chung,
  • triển khai lớp bộ điều hợp / bộ chặn để thêm loại điểm đánh dấu và sau đó
  • sử dụng loại chung chung là tên

Tôi đã viết một bài viết với nhiều chi tiết hơn: Dependency Injection in .NET: Một cách để khắc phục các đăng ký bị thiếu tên


Làm thế nào để chấp nhận trả lời vi phạm nguyên tắc trách nhiệm duy nhất?
LP13

Xem bình luận của stackoverflow.com/a/52066039/876814 và trong câu trả lời được chấp nhận, dịch vụ được giải quyết một cách lười biếng, tức là bạn chỉ biết nếu nó bị lỗi khi chạy và không có cách nào để kiểm tra tĩnh khi khởi động sau khi xây dựng container (tương tự như câu trả lời trong bình luận). SRP bởi vì dịch vụ không chỉ chịu trách nhiệm cho logic kinh doanh của nó mà còn cho độ phân giải phụ thuộc
Rico Suter

@RicoSuter Tôi thực sự thích giải pháp trong blog của bạn, nhưng bị nhầm lẫn bởi DI của bạn trong lớp Khởi động. Đặc biệt, tôi không hiểu dòng MessagePublisher ("MyOrderCreatedQueue") vì tôi không thấy một nhà xây dựng có chữ ký đó. services.AddSingleton <IMessagePublisher <OrderCreatedMessage >> (MessagePublisher mới <OrderCreatedMessage> (MessagePublisher mới ("MyOrderCreatedQueue")));
Lee Z

Cảm ơn, đã cập nhật bài viết và sử dụng MyMessagePublisher làm bản triển khai mẫu của IMessagePublisher
Rico Suter

7

Đến muộn bữa tiệc này, nhưng đây là giải pháp của tôi: ...

Startup.cs hoặc Program.cs nếu Trình xử lý chung ...

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

IMyInterface của Thiết lập giao diện T

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

Triển khai cụ thể IMyInterface của T

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

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

Hy vọng nếu có bất kỳ vấn đề nào khi thực hiện theo cách này, ai đó sẽ vui lòng chỉ ra lý do tại sao đây là cách sai để làm điều này.


2
IMyInterface<CustomerSavedConsumer>IMyInterface<ManagerSavedConsumer>các loại dịch vụ khác nhau - điều này hoàn toàn không trả lời câu hỏi của OP.
Richard Hauer

2
OP muốn có cách đăng ký nhiều triển khai của cùng một giao diện trong lõi Asp.net. Nếu tôi không làm điều này, xin vui lòng giải thích làm thế nào (chính xác).
Xám

1
Trong khi bạn đúng, mẫu này cho phép hiệu ứng mà op muốn. Ít nhất là khi tôi đang cố gắng tự làm điều này, tôi đã tình cờ thấy bài đăng này và giải pháp của tôi hoạt động tốt nhất cho tình huống của tôi.
Xám

1
Tôi hy vọng vấn đề là nhiều hơn khi đăng ký nhiều triển khai cho một giao diện (sử dụng MS DI) không cho phép bộ chứa phân biệt một triển khai này với một triển khai khác. Trong các DI khác, bạn có thể khóa chúng để container biết nên chọn cái nào. Trong MS bạn phải sử dụng một đại biểu và chọn thủ công. Giải pháp của bạn không giải quyết được tình huống này vì các giao diện của bạn khác nhau, do đó, container không có vấn đề gì trong việc chọn cách thực hiện đúng. Trong khi mẫu của bạn rõ ràng hoạt động, nó không phải là một giải pháp cho vấn đề như đã nêu.
Richard Hauer

3
@Gray Mặc dù bài đăng của bạn có một số báo chí xấu, tôi cảm ơn bạn đã đưa giải pháp này về phía trước. Nó cung cấp cho người đọc một tùy chọn khác để khắc phục các hạn chế trong .net lõi DI. Mặc dù nó có thể không trả lời trực tiếp câu hỏi của OP, nhưng nó cung cấp một giải pháp thay thế hoàn hảo, đó là tất cả những gì SO, phải không?
Neil Watson

5

Rõ ràng, bạn chỉ có thể tiêm IEn Vô số giao diện dịch vụ của bạn! Và sau đó tìm ví dụ mà bạn muốn sử dụng LINQ.

Ví dụ của tôi là cho dịch vụ AWS SNS nhưng bạn có thể làm tương tự cho bất kỳ dịch vụ được tiêm nào.

Khởi nghiệp

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

appsinstall.json

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

Nhà máy 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();
}

Bây giờ bạn có thể nhận dịch vụ SNS cho khu vực bạn muốn trong dịch vụ hoặc bộ điều khiển tùy chỉnh của bạn

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

Một cách tiếp cận nhà máy chắc chắn là khả thi. Một cách tiếp cận khác là sử dụng tính kế thừa để tạo các giao diện riêng được kế thừa từ IService, triển khai các giao diện được kế thừa trong các triển khai IService của bạn và đăng ký các giao diện được kế thừa thay vì cơ sở. Cho dù việc thêm một hệ thống phân cấp thừa kế hoặc các nhà máy là mẫu "đúng" đều phụ thuộc vào người bạn nói chuyện. Tôi thường phải sử dụng mẫu này khi giao dịch với nhiều nhà cung cấp cơ sở dữ liệu trong cùng một ứng dụng sử dụng chung, chẳng hạn nhưIRepository<T> , làm nền tảng để truy cập dữ liệu.

Giao diện ví dụ và triển khai:

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

Thùng đựng hàng:

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

5

Necromance.
Tôi nghĩ mọi người ở đây đang phát minh lại bánh xe - và thật tệ, nếu tôi có thể nói như vậy ...
Nếu bạn muốn đăng ký một thành phần theo khóa, chỉ cần sử dụng từ điển:

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

Và sau đó đăng ký từ điển với bộ sưu tập dịch vụ:

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

nếu sau đó bạn không muốn lấy từ điển và truy cập bằng từ khóa, bạn có thể ẩn từ điển bằng cách thêm phương thức tra cứu khóa bổ sung vào bộ sưu tập dịch vụ:
(việc sử dụng ủy nhiệm / đóng sẽ tạo cơ hội cho người bảo trì tiềm năng hiểu những gì đang diễn ra - ký hiệu mũi tên hơi khó hiểu)

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

Bây giờ bạn có thể truy cập các loại của bạn với một trong hai

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

hoặc là

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

Như chúng ta có thể thấy, cái đầu tiên hoàn toàn không cần thiết, bởi vì bạn cũng có thể làm chính xác điều đó với một từ điển, mà không yêu cầu đóng và AddTransient (và nếu bạn sử dụng VB, thậm chí cả niềng răng cũng sẽ khác):

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

(đơn giản hơn là tốt hơn - bạn có thể muốn sử dụng nó làm phương thức mở rộng)

Tất nhiên, nếu bạn không thích từ điển, bạn cũng có thể trang trí giao diện của mình bằng một thuộc tính Name(hoặc bất cứ thứ gì) và tra cứu nó bằng phím:

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

Nhưng điều đó đòi hỏi phải thay đổi giao diện của bạn để phù hợp với thuộc tính và việc lặp qua nhiều yếu tố sẽ chậm hơn nhiều so với tra cứu mảng kết hợp (từ điển).
Thật tuyệt khi biết rằng nó có thể được thực hiện mà không có dicionary, mặc dù.

Đây chỉ là 0,05 đô la của tôi


Nếu dịch vụ đã được IDisposethực hiện, ai chịu trách nhiệm xử lý dịch vụ? Bạn đã đăng ký từ điển dưới dạngSingleton
LP13

@ LP13: Bạn cũng có thể đăng ký từ điển với giá trị ủy nhiệm, sau đó bạn có thể đăng ký từ điển trong đó và tạo một phiên bản mới, ví dụ: GetRequiredService <T> () ["logDB"] ()
Stefan Steiger

5

kể từ bài đăng của tôi ở trên, tôi đã chuyển sang Lớp học chung

Sử dụng

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

Thực hiện

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

Sự mở rộng

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

Bạn có thể cung cấp mở rộng phương thức .AddFactory () không?
nhà phát triển

Xin lỗi Chỉ cần thấy điều này ... đã thêm
T Brown

3

Mặc dù có vẻ như @Miguel A. Arilla đã chỉ ra điều đó rõ ràng và tôi đã bỏ phiếu cho anh ta, tôi đã tạo ra trên giải pháp hữu ích của anh ta một giải pháp khác trông gọn gàng nhưng đòi hỏi nhiều công sức hơn.

Nó chắc chắn phụ thuộc vào giải pháp trên. Vì vậy, về cơ bản tôi đã tạo ra một cái gì đó tương tự Func<string, IService>>và tôi gọi nó IServiceAccessorlà một giao diện và sau đó tôi phải thêm một số tiện ích mở rộng IServiceCollectionkhác như sau:

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 dịch vụ trông giống như:

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

    }

Kết quả cuối cùng, bạn sẽ có thể đăng ký các dịch vụ với tên hoặc các trường hợp được đặt tên như chúng ta thường làm với các container khác..cho ví dụ:

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

Điều đó là đủ cho bây giờ, nhưng để làm cho công việc của bạn hoàn thành, tốt hơn là thêm nhiều phương thức mở rộng hơn khi bạn có thể bao gồm tất cả các loại đăng ký theo cùng một cách tiếp cận.

Có một bài đăng khác trên stackoverflow, nhưng tôi không thể tìm thấy nó, nơi người đăng đã giải thích chi tiết tại sao tính năng này không được hỗ trợ và cách khắc phục nó, về cơ bản tương tự như những gì @Miguel đã nêu. Đó là một bài viết hay mặc dù tôi không đồng ý với từng điểm vì tôi nghĩ có những trường hợp bạn thực sự cần những trường hợp được đặt tên. Tôi sẽ đăng liên kết đó ở đây khi tôi tìm thấy nó một lần nữa.

Vì thực tế, bạn không cần phải vượt qua Bộ chọn hoặc Accessor đó:

Tôi đang sử dụng mã sau đây trong dự án của tôi và nó đã hoạt động tốt cho đến nay.

 /// <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
Điều này đã giúp giải quyết vấn đề của tôi khi tôi mất đăng ký các loại trong trình truy cập dịch vụ. Bí quyết là loại bỏ tất cả các ràng buộc cho người truy cập dịch vụ và sau đó thêm lại!
Umar Farooq Khawaja

3

Tôi đã tạo ra một thư viện cho việc này thực hiện một số tính năng hay. Mã có thể được tìm thấy trên GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

Cách sử dụng rất đơn giản:

  1. Thêm gói nuget Dazinator.Extensions.DependencyInjection vào dự án của bạn.
  2. Thêm đăng ký dịch vụ được đặt tên của bạn.
    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.

    });

Trong ví dụ trên, lưu ý rằng đối với mỗi đăng ký được đặt tên, bạn cũng đang chỉ định thời gian tồn tại hoặc Singleton, Scoped hoặc Transient.

Bạn có thể giải quyết các dịch vụ theo một trong hai cách, tùy thuộc vào việc bạn có cảm thấy thoải mái với việc dịch vụ của mình có phụ thuộc vào gói này không:

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

hoặc là

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

Tôi đã đặc biệt thiết kế thư viện này để hoạt động tốt với Microsoft.Extensions.DependencyInjection - ví dụ:

  1. Khi bạn đăng ký dịch vụ đặt tên, bất kỳ loại mà bạn đăng ký có thể có nhà xây dựng với các thông số - họ sẽ được thỏa mãn qua DI, trong cùng một cách mà AddTransient<>, AddScoped<>AddSingleton<>phương pháp làm việc bình thường.

  2. Đối với các dịch vụ có tên tạm thời và có phạm vi, sổ đăng ký xây dựng ObjectFactoryđể nó có thể kích hoạt các phiên bản mới của loại rất nhanh khi cần. Điều này nhanh hơn nhiều so với các cách tiếp cận khác và phù hợp với cách Microsoft.Extensions.DependencyInjection thực hiện mọi việc.


2

Giải pháp của tôi cho những gì đáng để ... cân nhắc chuyển sang Castle Windsor vì không thể nói tôi thích bất kỳ giải pháp nào ở trên. Lấy làm tiếc!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Tạo các triển khai khác nhau của bạn

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

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

Đăng ký

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

Trình xây dựng và sử dụng cá thể ...

public class Whatever
{
   private IStage ClassA { get; }

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

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

1

Mở rộng giải pháp của @rnrneverdies. Thay vì ToString (), các tùy chọn sau cũng có thể được sử dụng- 1) Với việc triển khai thuộc tính chung, 2) Dịch vụ dịch vụ được đề xuất bởi @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

Sau khi đọc câu trả lời ở đây và các bài báo ở nơi khác, tôi đã có thể làm cho nó hoạt động mà không cần chuỗi. Khi bạn có nhiều triển khai của cùng một giao diện, DI sẽ thêm chúng vào bộ sưu tập, do đó có thể truy xuất phiên bản bạn muốn từ bộ sưu tập bằng cách sử dụng 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();
    }
}

Đánh bại mục đích của IoC. Bạn cũng có thể chỉ cần viết:var serviceA = new ServiceA();
James Curran

2
@JamesCurran không nếu ServiceA có phụ thuộc hoặc nếu bạn muốn đơn vị kiểm tra lớp.
Jorn.Beyers

0

Mặc dù việc triển khai ngoài hộp không cung cấp nó, đây là một dự án mẫu cho phép bạn đăng ký các thể hiện được đặt tên, sau đó đưa INamedServiceFactory vào mã của bạn và lấy ra các thể hiện theo tên. Không giống như các giải pháp mặt khác ở đây, nó sẽ cho phép bạn đăng ký nhiều phiên bản thực hiện giống nhau nhưng được cấu hình khác nhau

https://github.com/macsux/DotNetDINamedInstances


0

Làm thế nào về một dịch vụ cho các dịch vụ?

Nếu chúng ta có giao diện INamedService (với thuộc tính .Name), chúng ta có thể viết một phần mở rộng IServiceCollection cho .GetService (tên chuỗi), trong đó phần mở rộng sẽ lấy tham số chuỗi đó và tự thực hiện một .GetService () ví dụ, tìm trường hợp có INamedService.Name khớp với tên đã cho.

Như thế này:

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

Do đó, IMyService của bạn phải triển khai INamedService, nhưng bạn sẽ có được độ phân giải dựa trên khóa mà bạn muốn, phải không?

Công bằng mà nói, thậm chí phải có giao diện INamedService này có vẻ xấu, nhưng nếu bạn muốn đi xa hơn và làm cho mọi thứ trở nên thanh lịch hơn, thì có thể tìm thấy [NamedServiceAttribution ("A") trên mã triển khai / lớp trong mã này tiện ích mở rộng và nó cũng hoạt động tốt. Công bằng hơn nữa, Reflection rất chậm, do đó, việc tối ưu hóa có thể theo thứ tự, nhưng thành thật mà nói đó là thứ mà công cụ DI nên giúp đỡ. Tốc độ và sự đơn giản là mỗi người đóng góp lớn cho TCO.

Nói chung, không cần một nhà máy rõ ràng, bởi vì "tìm một dịch vụ được đặt tên" là một khái niệm có thể sử dụng lại và các lớp nhà máy không mở rộng như một giải pháp. Và một Func <> có vẻ tốt đẹp, nhưng một khối chuyển đổi là rất bleh , và một lần nữa, bạn sẽ được viết funcs thường xuyên như bạn muốn được viết Nhà máy. Bắt đầu đơn giản, có thể tái sử dụng, với ít mã hơn và nếu điều đó hóa ra không làm điều đó cho bạn, thì hãy đi phức tạp.


2
Đây được gọi là mẫu định vị dịch vụ và thường không phải là con đường tốt nhất để đi trừ khi bạn hoàn toàn phải đi
Joe Phillips

@JoePhillips Bạn có một số đầu vào như tại sao nó không phải là một giải pháp tốt? tôi yêu sự thanh lịch của nó Nhược điểm duy nhất tôi có thể nghĩ đến là tôi tạo ra một ví dụ của tất cả chúng mỗi khi bạn nhận được một.
Peter

2
@Peter Lý do chính là vì nó rất rất khó để làm việc với. Nếu bạn chuyển một đối tượng serviceLocator vào một lớp, thì không rõ ràng tất cả những phụ thuộc mà lớp đó sử dụng vì nó lấy tất cả chúng từ một đối tượng "thần" ma thuật. Hãy tưởng tượng bạn phải tìm tài liệu tham khảo về loại bạn muốn thay đổi. Khả năng đó về cơ bản sẽ biến mất khi bạn nhận được mọi thứ thông qua một đối tượng định vị dịch vụ. Con tiêm xây dựng rõ ràng và đáng tin cậy hơn nhiều
Joe Phillips

Tôi không biết. Sự rõ ràng không phải là một điểm trừ đối với tôi ... bởi vì nếu tôi quan tâm đến việc theo dõi cách các thành phần của mình tận dụng sự phụ thuộc của chúng, tôi sẽ có các bài kiểm tra đơn vị cho ... các bài kiểm tra không chỉ đề cập đến từng phụ thuộc mà còn giúp chúng tôi hiểu Làm thế nào mỗi phụ thuộc là cần thiết. Làm thế nào khác bạn sẽ nhận thức được điều đó, bằng cách đọc các nhà xây dựng?!?
Craig Brunetti

0

Tôi đã gặp vấn đề tương tự và tôi đã làm việc với một phần mở rộng đơn giản để cho phép các dịch vụ được đặt tên. Bạn có thể tìm thấy nó ở đây:

Nó cho phép bạn thêm nhiều dịch vụ (được đặt tên) như bạn muốn như thế này:

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

Thư viện cũng cho phép bạn dễ dàng thực hiện một "mẫu nhà máy" như thế này:

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

Hy vọng nó giúp


0

Tôi đã tạo tiện ích mở rộng của riêng mình trên tiện ích mở rộng đã IServiceCollectionsử dụng 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;
    }

ServiceCollectionTypeMapperlà một ví dụ singleton rằng bản đồ IService> NameOfService> Implementationnơi một giao diện có thể có nhiều triển khai với tên gọi khác nhau, điều này cho phép đăng ký loại hơn chúng ta có thể giải quyết khi wee nhu cầu và là một cách tiếp cận khác so với dịch vụ quyết nhiều để lựa chọn những gì chúng ta muốn.

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

Để đăng ký một dịch vụ mới:

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

Để giải quyết dịch vụ, chúng tôi cần một phần mở rộng IServiceProvidernhư thế này.

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

Khi giải quyết:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

Hãy nhớ rằng serviceProvider có thể được thêm vào trong hàm tạo trong ứng dụng của chúng tôi như IServiceProvider .

Tôi hi vọng cái này giúp được.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.