Как зарегистрировать несколько реализаций одного и того же интерфейса в Asp.Net Core?

240

У меня есть службы, которые получены из того же интерфейса.

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

Как правило, другие контейнеры IoC, такие как Unity позволяют вам регистрировать конкретные реализации некоторыми, Keyкоторые их различают.

В ASP.NET Core, как мне зарегистрировать эти сервисы и разрешить их во время выполнения на основе некоторого ключа?

Я не вижу каких-либо Addметодов Service, которые принимают параметр 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), потому что тогда я должен зарегистрировать все остальное (например, Controllers) в новом контейнере.

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

Итак, я думаю, что в стандартном DI в ASP.NET Core отсутствуют две вещи:

  1. Возможность регистрации экземпляров с помощью ключа
  2. Возможность вставлять статические данные в конструкторы при регистрации
LP13
источник
4
Возможный дубликат разрешения инъекций зависимости по имени
adem caglin
2
Наконец, в nuget добавлено расширение для регистрации по имени, надеюсь, это может помочь
neleus
Привет, извините за мой глупый вопрос, но я о новом с Microsoft.Extensions.DependencyInjection ... Вы думаете, что создаете 3 пустых интерфейса, которые расширяют Iservice как "открытый интерфейс IServiceA: IService" и чем "открытый класс ServiceA: IServiceA "... может быть хорошим вариантом практики?
Эмилиано
эта статья полезна? stevejgordon.co.uk/…
Майк Б,
Может Update1быть перенесен на другой вопрос, так как внедрение вещей в конструкторы сильно отличается от разработки, какой объект построить
Нейл

Ответы:

246

Я сделал простой обходной путь, используя, Funcкогда я оказался в этой ситуации.

Сначала объявите общего делегата:

public delegate IService ServiceResolver(string key);

Затем Startup.csнастройте несколько конкретных регистраций и сопоставьте их вручную:

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

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

И используйте его из любого класса, зарегистрированного в DI:

public class Consumer
{
    private readonly IService _aService;

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

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

Имейте в виду, что в этом примере ключом для разрешения является строка, для простоты и потому, что OP запрашивал именно этот случай.

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

Мигель А. Арилла
источник
1
@MatthewStevenMonkan обновил мой ответ примером
Мигель А.
2
Использование заводского шаблона, как это, - лучший способ. Спасибо, что поделился!
Сергей Акопов
2
+1 Очень аккуратно и чисто, потому что когда мы используем другие di-контейнеры, мы должны включать их пакет всякий раз, когда нам нужно разрешить зависимости, например. ILifetimeScope в AutoFac.
Анупам Сингх
1
@AnupamSingh На мой взгляд, большинству приложений малого и среднего размера, работающих на .NET Core, не требуется DI-инфраструктура, просто добавляется сложность и нежелательные зависимости, красоты и простоты встроенного DI более чем достаточно, и это может также будет легко расширяться.
Мигель А. Арилла
9
Объяснение понижения голосов - это очень интересно, но я в настоящее время занимаюсь рефакторингом массивной базы кода, чтобы удалить всю эту магию Func, которую кто-то сделал несколько лет назад (до революции MS DI). Проблема в том, что она значительно увеличивает сложность наложения свойств, которые может привести к запутанному разрешению DI дальше по линии. Например, я работал над обработчиком службы Windows, имел более 1,6 тыс. Строк кода для Func, и после того, как я сделал рекомендуемый способ ввода-вывода, я сократил его до 0,2 тыс. Строк. ОК-строки кода ничего не значат ... кроме того, что теперь его легче читать и использовать повторно ...
Петр Кула
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

rnrneverdies
источник
6
Не уверен, но я думаю, что это не детерминировано. Любые результаты, которые вы получаете сегодня, могут измениться завтра, это не очень хорошая практика.
rnrneverdies
4
upvote для ссылки на GetServices, которая показала мне, что вы можете запросить список услуг зависимого сервиса, запросивIEnumerable<IService>
Джонни 5
20
serviceProvider.GetServices <IService> () будет создавать экземпляры каждого из ServiceA, ServiceB и ServiceC. Вы бы хотели вызвать конструктор только одного сервиса - того, который вам действительно нужен. Это большая проблема, если реализации не легки или у вас много реализаций IService (например, у вас есть автоматически сгенерированные реализации IRepository для каждой модели).
Урос
6
Я согласен с @Uros. Это не хорошее решение. Представьте, что произойдет, если вы зарегистрируете 10 реализаций IService, а экземпляр, который вам действительно нужен, является последним. В этом случае DI фактически создает 9 экземпляров, которые никогда не используются.
Томаи
4
Плохая идея: множественные неиспользуемые экземпляры, анти-шаблон локатора службы и прямая связь с реальной реализацией (typeof <ServiceA>).
Рико Сутер
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 выше. Я скопировал это точно, только изменив это для моих типов. Это хорошее решение и работает отлично. Единственным недостатком является то, что я не могу использовать инъецированный контейнер и прибегаю к статическому контейнеру, чего я не хочу делать.
mohrtan
1
Это работает для меня, спасибо ГерардоГриньоли. @mohrtan пример кода здесь, если вы все еще изучаете это. github.com/Yawarmurtaza/AspNetCoreStructureMap
Явар Муртаза,
13

Вы правы, встроенный контейнер ASP.NET Core не имеет концепции регистрации нескольких сервисов и последующего извлечения конкретного, как вы предполагаете, фабрика - единственное реальное решение в этом случае.

Кроме того, вы можете переключиться на сторонний контейнер, такой как Unity или StructureMap, который предоставляет необходимое решение (см. Здесь: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- заместитель по умолчанию-услуги-контейнер ).

Носок
источник
13

Я столкнулся с той же проблемой и хочу рассказать, как я ее решил и почему.

Как вы упомянули, есть две проблемы. Первый:

В Asp.Net Core, как мне зарегистрировать эти сервисы и разрешить их во время выполнения, основываясь на каком-то ключе?

Итак, какие у нас есть варианты? Люди предлагают два:

  • Используйте собственную фабрику (как _myFactory.GetServiceByKey(key))

  • Используйте другой двигатель DI (как _unityContainer.Resolve<IService>(key))

Шаблон фабрики - единственный вариант здесь?

Фактически оба варианта являются фабриками, потому что каждый контейнер IoC также является фабрикой (хотя и с высокой степенью конфигурации и сложности). И мне кажется, что другие варианты также являются вариациями паттерна Factory.

Так какой вариант лучше? Здесь я согласен с @Sock, который предложил использовать собственную фабрику, и именно поэтому.

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

Во-вторых, часто нам нужно внедрить много именованных зависимостей для одного сервиса. В случае Unity вам может потребоваться указать имена для параметров конструктора (используя InjectionConstructor). Эта регистрация использует отражение и некоторую умную логику, чтобы угадать аргументы для конструктора. Это также может привести к ошибкам во время выполнения, если регистрация не соответствует аргументам конструктора. С другой стороны, при использовании вашей собственной фабрики вы имеете полный контроль над тем, как предоставить параметры конструктора. Это более читабельно и решается во время компиляции. Поцелуй принцип снова.

Вторая проблема:

Как _serviceProvider.GetService () может вставить соответствующую строку подключения?

Во-первых, я согласен с вами, что в зависимости от новых вещей, таких как IOptions(и, следовательно, от пакета Microsoft.Extensions.Options.ConfigurationExtensions) не очень хорошая идея. Я видел некоторые дискуссии о том, IOptionsгде были разные мнения о его пользе. Опять же, я стараюсь избегать добавления новых зависимостей, когда они действительно не нужны. Это действительно нужно? Я думаю нет. В противном случае каждая реализация должна зависеть от нее без какой-либо явной необходимости, исходящей от этой реализации (для меня это выглядит как нарушение ISP, где я тоже с вами согласен). Это также верно в отношении зависимости от завода, но в этом случае это может избежать.

Для этой цели ASP.NET Core DI обеспечивает очень хорошую перегрузку:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
Нелей
источник
Привет, извините за мой глупый вопрос, но я о новом с Microsoft.Extensions.DependencyInjection ... Вы думаете, что создаете 3 интерфейса, которые расширяют Iservice как "открытый интерфейс IServiceA: IService" и чем "открытый класс ServiceA: IServiceA" ... может быть хорошим вариантом практики?
Эмилиано
1
@ emiliano-magliocca В общем, вы не должны зависеть от интерфейсов, которые вы не используете (ISP), IServiceAв вашем случае. Поскольку вы используете методы from IServiceonly, у вас должна быть зависимость IServiceтолько от.
Нелеус
1
@ cagatay-kalan В случае вопроса ОП он может легко достичь своей цели с помощью ASP.NET Core DI. Нет необходимости в других структурах DI.
Нелеус
1
@EmilianoMagliocca Это может быть легко решено таким образом: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));для первого класса и services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));для второго.
Нелеус
1
В моем примере @EmilianoMagliocca и MyFirstClass, и MySecondClass имеют один и тот же параметр ctor типа интерфейса, который реализуют как Escpos, так и Usbpos. Таким образом, приведенный выше код только указывает контейнеру IoC, как создавать экземпляры «MyFirstClass» и «MySecondClass». Ничего более. Кроме того, вам может потребоваться сопоставить некоторые другие интерфейсы с MyFirstClass и MySecondClass. Это зависит от ваших потребностей, и я не рассмотрел это в своем примере.
Нелеус
13

Я просто ввожу IEnumerable

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

Extensions.cs

    public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
    {
        return assembly.GetTypesAssignableFrom(typeof(T));
    }
    public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
    {
        List<Type> ret = new List<Type>();
        foreach (var type in assembly.DefinedTypes)
        {
            if (compareType.IsAssignableFrom(type) && compareType != type)
            {
                ret.Add(type);
            }
        }
        return ret;
    }
Т Браун
источник
В методе DoSomething () контроллера вы можете использовать typeof для разрешения нужной вам службы: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Кьяран
Я буквально все перепробовал, и это единственное решение, которое сработало для меня. Спасибо!
Skatz1990
@ Skatz1990 Попробуйте решение, которое я создал ниже в другом посте. Я думаю, что это чище и проще в использовании.
T Браун
12

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

Еще один способ избежать этих проблем:

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

Я написал статью с более подробной информацией: Внедрение зависимостей в .NET: способ обойти пропущенные именованные регистрации

Рико Сутер
источник
как принятый ответ на фиалки принцип единственной ответственности?
LP13
См. Комментарии на stackoverflow.com/a/52066039/876814, а также в принятом ответе служба разрешается лениво, т. Е. Вы знаете только, если она выходит из строя во время выполнения, и нет способа статически проверить это при запуске после сборки контейнера (аналогично ответ в комментарии). SRP, потому что служба отвечает не только за свою бизнес-логику, но и за разрешение зависимостей
Rico Suter,
@RicoSuter Мне очень нравится решение в вашем блоге, но меня смущает ваш DI в классе Startup. В частности, я не понимаю строку MessagePublisher («MyOrderCreatedQueue»), поскольку я не вижу конструктор с этой подписью. services.AddSingleton <IMessagePublisher <OrderCreatedMessage >> (новый MessagePublisher <OrderCreatedMessage> (новый MessagePublisher ("MyOrderCreatedQueue")));
Ли З,
Спасибо, обновил статью и использовал MyMessagePublisher в качестве примера реализации IMessagePublisher
Рико Сутер
7

Немного опоздал на эту вечеринку, но вот мое решение: ...

Startup.cs или Program.cs если универсальный обработчик ...

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

Интерфейс IMyInterface T Interface

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

Конкретные реализации IMyInterface T

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

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

Надеемся, что если есть какие-то проблемы с этим, кто-то будет любезно указать, почему это неправильный способ сделать это.

Серый
источник
3
IMyInterface<CustomerSavedConsumer>и IMyInterface<ManagerSavedConsumer>это разные типы услуг - это не отвечает на вопросы ОП вообще.
Ричард Хауэр
2
ОП хотел способ регистрации нескольких реализаций одного и того же интерфейса в ядре Asp.net. Если я не сделал этого, пожалуйста, объясните, как (точно).
Серый
1
Хотя вы правы, этот паттерн позволяет получить эффект, которого хотел оп. По крайней мере, когда я пытался сделать это сам, я наткнулся на этот пост, и мое решение лучше всего подошло для моей ситуации.
Серый
1
Я ожидаю, что проблема заключалась в том, что регистрация нескольких реализаций для одного интерфейса (с использованием MS DI) не позволяет контейнеру отличать одну реализацию от другой. В других DI вы можете ввести их, чтобы контейнер знал, какой из них выбрать. В MS вы должны использовать делегата и выбирать вручную. Ваше решение не учитывает этот сценарий, так как ваши интерфейсы отличаются, поэтому у контейнера нет проблем с выбором правильной реализации. Хотя ваш пример, очевидно, работает, это не решение проблемы, как указано.
Ричард Хауэр
3
@ Серый Несмотря на то, что ваш пост получил плохую репутацию, я благодарю вас за то, что вы выдвинули это решение. Это дает читателям еще одну возможность преодолеть ограничения в .net core DI. Хотя он может и не дать прямого ответа на вопрос OP, он предоставляет идеальное альтернативное решение, в чем суть SO, верно?
Нил Уотсон
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 Factory

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

     .........
}
ArcadeRenegade
источник
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>();
jlc397
источник
5

Necromancing.
Я думаю, что люди здесь заново изобретают колесо - и плохо, если можно так сказать ...
Если вы хотите зарегистрировать компонент по ключу, просто используйте словарь:

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

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

А затем зарегистрируйте словарь в сервис-коллекции:

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

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

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

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

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

Теперь вы можете получить доступ к вашим типам с

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

или

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

Как мы видим, первый из них просто лишний, потому что вы можете сделать это точно со словарем, не требуя замыканий и AddTransient (и если вы используете VB, даже скобки не будут другими):

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

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

Конечно, если вам не нравится словарь, вы также можете снабдить свой интерфейс свойством Name(или чем-то еще) и посмотреть его по ключу:

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



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

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

                return null;
            };
    });

Но для этого необходимо изменить интерфейс, чтобы приспособить свойство, и циклический просмотр множества элементов должен быть намного медленнее, чем поиск по ассоциативному массиву (словарь).
Приятно осознавать, что это можно сделать без диктиона.

Это всего лишь мои $ 0,05

Стефан Штайгер
источник
Если служба IDisposeреализована, кто несет ответственность за ее использование? Вы зарегистрировали словарь какSingleton
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 Браун
3

Хотя, кажется, @Miguel A. Arilla четко указал на это, и я проголосовал за него, я создал поверх его полезного решения еще одно решение, которое выглядит аккуратно, но требует гораздо больше работы.

Это определенно зависит от вышеуказанного решения. В общем, я создал нечто похожее Func<string, IService>>и назвал это IServiceAccessorинтерфейсом, а затем мне пришлось добавить еще несколько расширений IServiceCollectionкак таковых:

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

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

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

Accessor службы выглядит так:

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

    }

В результате вы сможете зарегистрировать сервисы с именами или именованными экземплярами, как мы это делали с другими контейнерами. Например:

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

Пока этого достаточно, но чтобы завершить работу, лучше добавить больше методов расширения, чтобы охватить все типы регистраций, следуя тому же подходу.

Был еще один пост о stackoverflow, но я не могу найти его, где автор подробно объяснил, почему эта функция не поддерживается и как ее обойти, в основном аналогично тому, что сказал @Miguel. Это был хороший пост, хотя я не согласен с каждым пунктом, потому что я думаю, что есть ситуации, когда вам действительно нужны именованные экземпляры. Я опубликую эту ссылку здесь, как только найду ее снова.

На самом деле вам не нужно передавать этот селектор или аксессор:

Я использую следующий код в своем проекте, и до сих пор он работал хорошо.

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

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

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

    /// <summary>
    /// Registers an interface for a class
    /// </summary>
    /// <typeparam name="TInterface">The type of the t interface.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection As<TInterface>(this IServiceCollection services)
         where TInterface : class
    {
        var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
        if (descriptor.IsNotNull())
        {
            var provider = services.BuildServiceProvider();
            var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
            services?.AddSingleton(implementationInstance);
        }
        return services;
    }
Assil
источник
1
Это помогло решить мою проблему, когда я терял регистрацию типов в службе доступа. Хитрость заключалась в том, чтобы удалить все привязки для средства доступа к службе, а затем добавить его снова!
Умар Фарук Хаваджа
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 или Transient.

Вы можете разрешить услуги одним из двух способов, в зависимости от того, насколько вам удобны зависимости от этих пакетов:

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

или

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

Я специально разработал эту библиотеку для работы с Microsoft.Extensions.DependencyInjection - например:

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

  2. Для временных и указанных именованных служб реестр создает ObjectFactoryтак, что он может очень быстро активировать новые экземпляры типа при необходимости. Это намного быстрее, чем другие подходы, и соответствует тому, как Microsoft.Extensions.DependencyInjection работает.

Даррелл
источник
2

Мое решение для того, что оно стоит ... подумал о переходе на замок Виндзор, так как не могу сказать, что мне понравилось любое из решений выше. Сожалею!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Создайте свои различные реализации

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

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

Постановка на учет

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

Использование конструктора и экземпляра ...

public class Whatever
{
   private IStage ClassA { get; }

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

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }
littgle
источник
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....
}
vrluckyin
источник
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/DotNetDINamedInstances

Андрей Стахов
источник
0

Как насчет услуги для услуг?

Если бы у нас был интерфейс INamedService (со свойством .Name), мы могли бы написать расширение IServiceCollection для .GetService (имя строки), где расширение будет принимать этот строковый параметр, и делать .GetServices () для себя и в каждом возвращаемом instance, найдите экземпляр, чье имя 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
    }

Надеюсь, поможет

Subgurim
источник
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 .

Надеюсь, это поможет.

svladimirrc
источник