Як зареєструвати кілька реалізацій одного інтерфейсу в 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?

Я не бачу жодних 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), тому що мені доведеться реєструвати все інше (наприклад, контролери) і з новим контейнером.

Крім того , використовуючи шаблон фабрики для створення екземплярів служб проти DIP, оскільки це збільшує кількість залежностей клієнт має подробиці тут .

Отже, я думаю, що в замовчуванні DI в ASP.NET Core відсутні дві речі:

  1. Можливість реєстрації екземплярів за допомогою ключа
  2. Можливість введення статичних даних у конструктори під час реєстрації


2
Нарешті, є розширення на цілі для реєстрацій на основі імен, сподіваюся, що це може допомогти
neleus

Привіт, вибачте за моє дурне запитання, але я про нове з Microsoft.Extensions.DependencyInjection ... ви вважаєте, що створюєте 3 порожні інтерфейси, які розширюють Iservice, як-от "публічний інтерфейс IServiceA: IService" і ніж "публічний клас ServiceA: IServiceA "... може бути гарним варіантом практики?
Emiliano Magliocca

ця стаття будь-якого використання? stevejgordon.co.uk/…
Майк Б

Можна Update1перейти до іншого питання, оскільки впорскування речей у конструктори сильно відрізняється від розробки того, який об’єкт спорудити
Ніл

Відповіді:


245

Я зробив просте вирішення, використовуючи, Funcколи опинився в цій ситуації.

По-перше, оголосити спільного делегата:

public delegate IService ServiceResolver(string key);

Потім у вашій програмі Startup.csвстановіть декілька конкретних реєстрацій та вручну картографуйте такі типи:

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

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

І використовувати його з будь-якого класу, зареєстрованого в DI:

public class Consumer
{
    private readonly IService _aService;

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

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

Майте на увазі, що в цьому прикладі ключовим для вирішення є рядок, для простоти і тому, що ОП просила саме про цю справу.

Але ви можете використовувати будь-який тип користувальницької роздільної здатності як ключ, оскільки зазвичай не хочеться, щоб величезний n-регістр комутатора гнив ваш код. Залежить від масштабування вашої програми.


1
@MatthewStevenMonkan оновив мою відповідь прикладом
Мігель А. Арілла

2
Використання подібного фабричного візерунка - це найкращий шлях. Дякую, що поділились!
Сергій Акопов

2
+1 Дуже акуратно і чисто, тому що, коли ми використовуємо інший контейнер, ми повинні включати їхній пакет, коли нам потрібно вирішити залежності, наприклад. ILifetimeScope в AutoFac.
Анупам Сінгх

1
@AnupamSingh На мою думку, більшість видів малих та середніх додатків, що працюють на .NET Core, не потребують жодної рамки DI, просто додають складності та небажаних залежностей, краси та простоти вбудованого DI більш ніж достатньо, і це може також можна легко розширити.
Мігель А. Арілья

7
Пояснення голосування вниз - Це дуже цікаво, але в даний час я перетворюю масивну базу коду, щоб видалити всю цю функцію магії, яку хтось робив кілька років тому (до революції MS DI). може викликати згорнуту роздільну здатність DI далі вниз по лінії. Наприклад, я працював на обробці служб Windows, маючи понад 1,6 К рядків коду, пов'язаних з Func, і після виконання рекомендованого способу DI я скоротив його до 0,2k рядків. ОК-рядки коду нічого не означають .. окрім його легшого для читання та повторного використання зараз ...
Piotr Kula

79

Інший варіант - використовувати метод розширення GetServicesвід Microsoft.Extensions.DependencyInjection.

Зареєструйте свої послуги як:

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

Потім вирішіть з невеликою кількістю Linq:

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

або

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

(якщо припустити, що IServiceмає властивість рядка під назвою "Ім'я")

Обов’язково мати using Microsoft.Extensions.DependencyInjection;

Оновлення

Джерело AspNet 2.1: GetServices


6
Не впевнений, але я думаю, що це не детерміновано. Будь-які результати, які ви отримаєте сьогодні, можуть змінитися завтра, це здається недоброю практикою.
rnrneverdies

4
надішліть посилання на GetServices, яке показало мені, що ви можете запитати список послуг залежною службою, запитуючиIEnumerable<IService>
johnny 5

20
serviceProvider.GetServices <IService> () інстанціює кожен ServiceA, ServiceB та ServiceC. Ви хочете зателефонувати конструктору лише однієї послуги - тієї, яка вам насправді потрібна. Це велика проблема, якщо впровадження не відрізняється малою вагою або у вас є багато реалізацій IService (наприклад, у вас є автоматично створені реалізації IRepository для кожної моделі).
Урос

6
Я згоден з @Uros. Це не гарне рішення. Уявіть, що станеться, якщо ви зареєструєте 10 реалізацій IService, а фактичний потрібний вам екземпляр - останній. У цьому випадку 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 для автоматичного введення?
мортан

До речі, я використовую StructureMap.Micorosoft.DependencyInjection 1.3.0.
мортан

Повертаєте новий контейнер у ConfigureServices?
Джерардо Гриньолі

Повертаю IServiceProviderInstance нового контейнера, як зазначено в кроці 2 вище. Я скопіював, що точно змінив це лише для моїх типів. Це хороше рішення і працює чудово. Єдиним недоліком є ​​те, що я не в змозі використовувати ін'єкційний контейнер і вдаюсь до статичного контейнера, чого я не хочу робити.
мортан

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- контейнер-default-services-контейнер ).


13

Я зіткнувся з тим же питанням і хочу поділитися тим, як я його вирішив і чому.

Як ви вже згадували, є дві проблеми. Перший:

Як я можу зареєструвати ці служби та вирішити їх під час виконання в Asp.Net Core на основі якогось ключа?

То які варіанти у нас є? Люди пропонують два:

  • Використовувати власну фабрику (наприклад _myFactory.GetServiceByKey(key))

  • Використовуйте інший двигун DI (наприклад _unityContainer.Resolve<IService>(key))

Чи єдиний варіант тут?

Насправді обидва варіанти - це фабрики, тому що кожен контейнер IoC також є заводом (хоча він дуже налаштований і складний). І мені здається, що інші варіанти також є варіаціями фабричного візерунка.

То який варіант краще тоді? Тут я погоджуюся з @Sock, який запропонував використовувати власну фабрику, і саме тому.

По-перше, я завжди намагаюся уникати нових залежностей, коли вони насправді не потрібні. Тож я згоден з вами в цьому пункті. Більше того, використання двох фреймворків DI гірше, ніж створення власної заводської абстракції. У другому випадку вам потрібно додати нову залежність від пакунків (наприклад, Unity), але залежно від нового заводського інтерфейсу тут менше зла. Основна ідея ASP.NET Core DI, я вважаю, це простота. Він підтримує мінімальний набір функцій за принципом KISS . Якщо вам потрібна додаткова функція, то зробіть або зробіть відповідний Plungin, який реалізує потрібну функцію (Open Closed Principle).

По-друге, часто нам потрібно вводити багато названих залежностей для однієї послуги. У випадку Unity вам, можливо, доведеться вказати назви параметрів конструктора (використовуючи InjectionConstructor). Ця реєстрація використовує роздуми та розумну логіку для відгадування аргументів для конструктора. Це також може призвести до помилок виконання, якщо реєстрація не відповідає аргументам конструктора. З іншого боку, при використанні власної фабрики ви маєте повний контроль над тим, як забезпечити параметри конструктора. Він легше читається і вирішується під час компіляції. Знову принцип KISS .

Друга проблема:

Як _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.Extensions.DependencyInjection ... ви думаєте, що створюєте 3 інтерфейси, які розширюють Iservice, як "публічний інтерфейс IServiceA: IService", а ніж "Public Class ServiceA: IServiceA" ... може бути гарним варіантом практики?
Emiliano Magliocca

1
@ emiliano-magliocca Взагалі, IServiceAу вашому випадку ви не повинні залежати від інтерфейсів, якими ви не користуєтесь (ISP) . Оскільки ви використовуєте IServiceлише методи , ви повинні мати залежність IServiceлише від них.
neleus

1
@ cagatay-kalan У випадку питання про ОП він може легко досягти своєї мети за допомогою 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

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

Розширення.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

Я буквально все намагався, і це єдине рішення, яке працювало на мене. Дякую!
Skatz1990

@ Skatz1990 Спробуйте рішення, яке я створив нижче, в іншій публікації. Я думаю, що вона чистіша і простіша у використанні.
Т Браун

12

Більшість відповідей тут порушують принцип єдиної відповідальності (клас обслуговування не повинен сам вирішувати залежності) та / або використовувати антидіаграму службового локатора.

Ще одним варіантом уникнення цих проблем є:

  • використовувати додатковий параметр загального типу в інтерфейсі або новий інтерфейс, що реалізує не загальний інтерфейс,
  • застосуйте клас адаптера / перехоплювача, щоб додати тип маркера, а потім
  • використовувати загальний тип як "ім'я"

Я написав статтю з більш детальною інформацією: Введення залежностей у .NET: спосіб подолати відсутніх іменованих реєстрацій


як прийнята відповідь фіолетовим принципом єдиної відповідальності?
LP13

Дивіться коментарі stackoverflow.com/a/52066039/876814, а також у прийнятій відповіді послуга вирішено ледаче, тобто ви знаєте лише, якщо вона не працює під час виконання, і немає способу статично перевірити це при запуску після збирання контейнера (подібно до відповідь у коментарі). SRP, оскільки сервіс не тільки відповідає за свою бізнес-логіку, але і за вирішення залежності
Rico Suter

@RicoSuter Мені дуже подобається рішення у вашому блозі, але мене бентежить ваш DI в класі Startup. Конкретно, я не розумію рядок MessagePublisher ("MyOrderCreateQueue"), оскільки не бачу конструктора з цією підписом. services.AddSingleton <IMessagePublisher <OrderCreateMessage>> (новий MessagePublisher <OrderCreateMessage> (новий MessagePublisher ("MyOrderCreateQueue"));
Лі Z

Дякуємо, оновили статтю та використовуйте MyMessagePublisher як зразок реалізації IMessagePublisher
Rico Suter

7

Трохи спізнився на цю вечірку, але ось моє рішення: ...

Startup.cs або Program.cs, якщо Generic Handler ...

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

Налаштування IMyInterface T інтерфейсу

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 питання взагалі.
Річард Хауер

2
ОП бажала способу реєстрації декількох реалізацій одного інтерфейсу в ядрі Asp.net. Якщо я цього не зробив, будь ласка, поясніть, як (саме).
Сірий

1
Незважаючи на те, що ви маєте рацію, ця модель дозволяє досягти ефекту, якого хотіли оп. Принаймні, коли я намагався це зробити сам, я натрапив на цю посаду, і моє рішення найкраще працювало для моєї ситуації.
Сірий

1
Я думаю, що проблема була в тому, що реєстрація декількох реалізацій для одного інтерфейсу (використовуючи MS DI) не дозволяє контейнеру відрізняти одну реалізацію від іншої. В інших DI можна вводити їх, щоб контейнер знав, який вибрати. У MS ви повинні використовувати делегата та вибрати вручну. Ваше рішення не стосується цього сценарію, оскільки ваші інтерфейси різні, тому контейнер не має проблеми з вибором правильної реалізації. Хоча ваш зразок, очевидно, працює, це не є вирішенням проблеми, як заявлено.
Річард Хауер

3
@Gray Незважаючи на те, що у вашій публікації була погана преса, я дякую за те, що ви запропонували це рішення. Це дає читачам ще один варіант подолання обмежень у .net ядрах DI. Хоча він може не відповісти на питання ОП безпосередньо, але він пропонує ідеальне альтернативне рішення, яке саме таке все, правда?
Ніл Уотсон

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

Некромантування.
Я думаю, що тут люди винаходять колесо - і погано, якщо можна так сказати ...
Якщо ви хочете зареєструвати компонент за ключем, просто використовуйте словник:

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

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

А потім зареєструйте словник у службі-колекції:

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

якщо ви не бажаєте отримати словник і отримати доступ до нього за ключем, ви можете приховати словник, додавши додатковий метод пошуку ключів до колекції служби:
(використання делегата / закриття повинно дати потенційному обслуговуючому шансу можливість розуміння того, що відбувається - стрілка-позначення трохи загадкова)

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

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

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

Тепер ви можете отримати доступ до своїх типів за допомогою будь-якого

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

або

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

Як ми бачимо, перший - просто зайвий, тому що ви також можете зробити саме це зі словником, не вимагаючи закриття та AddTransient (і якщо ви використовуєте VB, навіть дужки не будуть різними):

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

(чим простіше, тим краще - ви можете використовувати його як метод розширення)

Звичайно, якщо словник вам не подобається, ви також можете спорядити свій інтерфейс із властивістю Name(чи будь-яким іншим) та переглянути це за ключем:

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



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

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

                return null;
            };
    });

Але для цього потрібна зміна вашого інтерфейсу для розміщення властивості, і перегляд багатьох елементів повинен бути набагато повільніше, ніж пошук асоціативного масиву (словник).
Приємно знати, що це можна зробити і без дикіонерства.

Це лише мої 0,05 долара


Якщо служба IDisposeреалізована, хто несе відповідальність за розпорядження послугою? Ви зареєстрували словник якSingleton
LP13,

@ LP13: Ви також можете зареєструвати словник з делегатом як значення, тоді ви можете зареєструвати його в itransient та створити новий екземпляр, наприклад. GetRequiredService <T> () ["logDB"] ()
Стефан Штайгер

5

з моєї посади вище, я перейшов на загальний заводський клас

Використання

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

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

Впровадження

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

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

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

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

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

Розширення

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

Чи можете ви надати розширення методу .AddFactory ()?
розробник

Вибачте Щойно побачив це ... додано
T Brown

3

Хоча здається, що @Miguel A. Арілла це чітко підкреслив, і я проголосував за нього, я створив поверх його корисного рішення ще одне рішення, яке виглядає акуратно, але вимагає значно більше роботи.

Це безумовно залежить від наведеного рішення. Отже, я створив щось подібне, 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
Це допомогло вирішити мою проблему, коли я втрачав реєстрацію типів у службі доступу. Хитрість полягала в тому, щоб видалити всі прив’язки для сервісного доступу та знову додати його!
Umar Farooq Khawaja

3

Я створив для цього бібліотеку, яка реалізує деякі приємні функції. Код можна знайти на GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

Використання просте:

  1. Додайте до свого проекту нульовий пакет Dazinator.Extensions.DependencyInjection.
  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, або тимчасовий.

Ви можете вирішити послуги одним із двох способів, залежно від того, чи вам подобається, щоб ваші послуги взяли залежність від цього пакета не:

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.Extensions.DependencyInjection - наприклад:

  1. Коли ви реєструєте іменовані служби, будь-які типи, які ви реєструєте, можуть мати конструктори з параметрами - вони будуть задоволені через DI, таким же чином AddTransient<>, як AddScoped<>і AddSingleton<>методи, як правило, працюють.

  2. Для тимчасових і наборених іменованих служб реєстр створює ObjectFactoryтак, щоб він міг швидко активувати нові екземпляри типу. Це набагато швидше, ніж інші підходи, і відповідає тому, як робить Microsoft.Extensions.DependencyInjection.


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();
Джеймс Курран

2
@JamesCurran не, якщо ServiceA має залежності, або якщо ви хочете перевірити клас класу.
Jorn.Beyers

0

Хоча реалізація поза коробкою не пропонує її, ось приклад проекту, який дозволяє зареєструвати іменовані екземпляри, а потім ввести INamedServiceFactory у свій код і витягнути екземпляри по імені. На відміну від інших факультативних рішень тут, це дозволить вам зареєструвати кілька екземплярів однієї реалізації, але налаштовано по-іншому

https://github.com/macsux/DotNetDINamedInsances


0

Як щодо послуги для послуг?

Якби у нас був інтерфейс INamedService (з властивістю .Name), ми могли б написати розширення IServiceCollection для .GetService (ім'я рядка), де розширення візьме цей параметр рядка і зробить .GetServices () на собі, і в кожному поверненому наприклад, знайдіть екземпляр, ім'я якого INamedService.Name відповідає заданому імені.

Подобається це:

public interface INamedService
{
    string Name { get; }
}

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

Тому ваша IMyService повинна реалізовувати INamedService, але ви отримаєте потрібне роздільне рішення, чи не так?

Якщо чесно, мати навіть цей інтерфейс INamedService здається некрасивим, але якщо ви хочете піти далі та зробити речі більш елегантними, тоді [NamedServiceAttribute ("A")] щодо реалізації / класу можна знайти за кодом у цьому розширення, і воно буде працювати так само добре. Якщо бути ще більш справедливим, відбиття повільне, тому оптимізація може бути в порядку, але, чесно кажучи, це те, що двигун DI повинен був допомогти. Швидкість та простота - це кожен із головних учасників TCO.

Загалом, немає явної фабрики, тому що "пошук іменованого сервісу" є такою концепцією для багаторазового використання, а фабричні класи не розглядаються як рішення. І Func <> здається прекрасним, але блок комутаторів настільки вигадливий , і знову ж таки, ви будете писати Funcs так часто, як ви пишете Фабрики. Почніть просте, багаторазове використання, з меншим кодом, і якщо це виявиться, що це не зробити для вас, тоді переходьте до складних.


2
Це називається схемою локатора служб - це, як правило, не найкращий маршрут, якщо вам абсолютно не доведеться
Джо Філіпс

@JoePhillips У вас є деякі дані щодо того, чому це не гарне рішення? я люблю його елегантність. Єдиний недолік, про який я можу подумати, - це те, що я створюю екземпляр усіх із них щоразу, коли отримуєш його.
Петро

2
@ Peter Основна причина полягає в тому, що з цим дуже важко працювати. Якщо ви передаєте об’єкт serviceLocator в клас, то зовсім не очевидно, які саме залежності використовує клас, оскільки він отримує їх усіх від магічного об’єкта "бог". Уявіть, що вам потрібно знайти посилання типу, який ви хочете змінити. Ця здатність в основному зникає, коли ви отримуєте все через об’єкт локалізації сервісу.
Джо Філіпс

Я не знаю. Очевидність не є мінусом для мене ... тому що, якби я піклувався про те, щоб мої компоненти втримували свої залежності, я мав би тести для цього ... тести, які не лише стосуються кожної залежності, але допомагають нам зрозуміти ЯК потрібна кожна залежність. Як ще ви це будете усвідомлювати, читаючи конструктори?!?
Крейг Брунетті

0

Я зіткнувся з тією ж проблемою, і я працював із простим розширенням, щоб дозволити іменовані послуги. Ви можете знайти його тут:

Це дозволяє додавати стільки (названих) служб, скільки ви хочете, як це:

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

 var serviceProvider = serviceCollection.BuildServiceProvider();

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

Бібліотека також дозволяє легко реалізувати "заводський зразок", як це:

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

        serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

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

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

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

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

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

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

Сподіваюся, це допомагає


0

Я створив власне розширення для IServiceCollectionвикористовуваного WithNameрозширення:

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

ServiceCollectionTypeMapperодноелементна екземпляр, карти IService> NameOfService> Implementationде інтерфейс може мати безліч реалізацій з різними іменами, це дозволяє реєструвати типи , ніж ми можемо вирішити , коли дитя потреба і інший підхід , ніж рішучість кілька послуг , щоб вибрати те , що ми хочемо.

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

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

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

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

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

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

Щоб зареєструвати нову послугу:

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

Для вирішення послуги нам потрібно розширення на IServiceProviderзразок цього.

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

При вирішенні:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

Пам'ятайте, що serviceProvider можна вводити в конструктор у нашому додатку як IServiceProvider .

Я сподіваюся, що це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.