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

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

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 here of the same interface            
    }


    public MyController:Controller
    {
       public void DoSomeThing(string key)
       { 
          // How do get service based on key
       }
    }

Является ли шаблон Factory единственным вариантом здесь?

Update1
Я прочитал здесь статью, в которой показано, как использовать шаблон фабрики для получения экземпляров службы, когда у нас есть несколько попыток создания конкретизации. Однако это еще не полное решение. когда я _serviceProvider.GetService() метод _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 или любом другом МОК мы можем это сделать во время регистрации типа. Я могу использовать IOption, однако мне потребуется ввести все настройки, я не могу ввести конкретную строку подключения в службу.

Также обратите внимание, что я стараюсь избегать использования других контейнеров (включая Unity), потому что тогда мне нужно зарегистрировать все остальное (например, контроллеры) с новым контейнером.

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

Поэтому я думаю, что по умолчанию DI в ядре ASP.NET отсутствует 2 вещи
1> Регистрация экземпляров с использованием ключа
2> Вставлять статические данные в конструктор во время регистрации

Ответ 1

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

services.AddTransient<Consumer>();

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

services.AddTransient<Func<string, IService>>(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 Func<string, IService> serviceAccessor;

    public Consumer(Func<string, IService> serviceAccessor)
    {
        this.serviceAccessor = serviceAccesor;
    }

    public void UseServiceA()
    {
        //use serviceAccessor field to resolve desired type
        serviceAccessor("A").DoIServiceOperation();
    }
}

ОБНОВИТЬ

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

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

ОБНОВЛЕНИЕ 2

Для удобства чтения вы можете обернуть Func<string, IService> внутри delegate такого как public delegate IService ServiceResolver(string key), и использовать вместо него ServiceResolver, где вы будете использовать версию Func.

Ответ 2

Другой вариант - использовать метод расширения 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 имеет свойство string, называемое "Name")

Обязательно using Microsoft.Extensions.DependencyInjection;

Обновить

Источник AspNet 2.1: GetServices

Ответ 3

Он не поддерживается Microsoft.Extensions.DependencyInjection.

Но вы можете подключить другой механизм инъекции зависимостей, например StructureMap Просмотреть домашнюю страницу и Проект GitHub.

Это не сложно:

  • Добавьте ссылку на StructureMap в свой project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
    
  • Внесите его в конвейер ASP.NET внутри ConfigureServices и зарегистрируйте свои классы (см. docs)

    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>();
    }
    
  • Затем, чтобы получить именованный экземпляр, вам нужно будет запросить 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; }
    }

Ответ 4

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

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

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

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

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

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

Является ли шаблон factory единственным вариантом здесь?

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

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

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

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

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

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

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

Ядро 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>())));

Ответ 5

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

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

Ответ 6

Я просто добавляю 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;
    }

Ответ 7

По-видимому, вы можете просто ввести 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();
    }

     .........
}

Ответ 8

Хотя кажется, что @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;
        }

Аксессуар службы выглядит так:

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

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

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

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

Ответ 9

Подход

A factory, безусловно, жизнеспособен. Другой подход заключается в использовании наследования для создания отдельных интерфейсов, которые наследуют от IService, реализации унаследованных интерфейсов в реализациях IService и регистрации унаследованных интерфейсов, а не базы. Является ли добавление иерархии наследования или фабрик "правильным" шаблоном, все зависит от того, с кем вы говорите. Мне часто приходится использовать этот шаблон при работе с несколькими поставщиками баз данных в одном приложении, которое использует общий тип, например IRepository <T>, в качестве основы для доступа к данным.

Примеры интерфейсов и реализаций:

 <код> открытый интерфейс IService
{
}

открытый интерфейс IServiceA: IService
{}

открытый интерфейс IServiceB: IService
{}

общественный IServiceC: IService
{}

публичный класс ServiceA: IServiceA
{}

открытый класс ServiceB: IServiceB
{}

открытый класс ServiceC: IServiceC
{}
Код>

Контейнер:

  container.Register < IServiceA, ServiceA >();
контейнер. Реестр < IServiceB, ServiceB >();
контейнер. Реестр < IServiceC, ServiceC >();
Код>

Ответ 10

Мое решение для того, что стоит... считается переходом на 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();
        .....
   }

Ответ 11

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

Startup.cs или Program.cs, если общий обработчик...

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

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

Ответ 12

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



// https://stackoverflow.com/questions/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

Ответ 13

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

https://github.com/macsux/DotNetDINamedInstances

Ответ 14

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

Если бы у нас был интерфейс 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")] для реализации/класса может быть найден кодом в этом расширение, и он будет работать так же хорошо. Чтобы быть еще более справедливым, Reflection работает медленно, поэтому оптимизация может быть в порядке, но, честно говоря, что-то, с чем мог бы помочь движок DI. Скорость и простота - это великие вкладчики в ТШО.

В общем, нет необходимости в явной фабрике, потому что "поиск именованной услуги" является такой многоразовой концепцией, а заводские классы не масштабируются как решение. И Func <> кажется прекрасным, но блок переключателей настолько бледен, и снова вы будете писать Funcs так часто, как вы писали бы Фабрики. Начните простую, многоразовую, с меньшим количеством кода, и если это не будет сделано для ya, тогда перейдите в сложный.

Ответ 15

Извините, что испортил вечеринку, но там были представлены отличные решения для неправильной проблемы. Внедрение зависимостей, которое пытается использовать автор вопроса, представлялось в качестве инструмента, разрешающего параметры конструктора, определяющего из типа параметра, какой фактический объект передать ему. Таким образом, он был задуман как картограф от типа к объекту.

Лучший ответ в теме - проблема интеграции фабричного шаблона в среду внедрения зависимостей ядра .NET. Отличная работа, ребята.

Чтобы углубиться в тему, советуетесь ли вы с рекомендациями по внедрению форм Windows в контроллеры MVC?

Ответ 16

Расширяя решение @rnrneverdies. Вместо ToString() также могут быть следующие опции: used- 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....
}

Ответ 17

В моем случае я просто использую это:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddTransient<IDriverActions, DriverA>();
    services.AddTransient<IDriverActions, DriverB>();
}

Мой контроллер:

public class DriverAController : ControllerBase
{
    readonly IDriverActions _driverAActions;

    public DriverAController(IEnumerable<IDriverActions> services)
    {
        _driverAActions = services.FirstOrDefault(s => s.GetType() == typeof(DriverA);
    }
...

Ответ 18

Я когда-то делал что-то подобное, и я думаю, что реализация была приятной.

Скажем, что вам нужно обрабатывать входящие сообщения, каждое сообщение имеет тип, поэтому общий подход здесь - использовать коммутатор и делать такие вещи:

@Override
public void messageArrived(String type, Message message) throws Exception {
    switch(type) {
        case "A": do Something with message; break;
        case "B": do Something else with message; break;
        ...
    }
}

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

@Override
public void messageArrived(String type, Message message) throws Exception {
     messageHandler.getMessageHandler(type).handle(message);
}

Это хорошо? Итак, как мы можем добиться чего-то подобного?

Сначала определим аннотацию, а также интерфейс

@Retention(RUNTIME)
@Target({TYPE})
public @interface MessageHandler {
     String value()
}

public interface Handler {
    void handle(MqttMessage message);
}

и теперь можно использовать эту аннотацию в классе:

@MessageHandler("myTopic")
public class MyTopicHandler implements Handler {

    @override
    public void handle(MqttMessage message) {
        // do something with the message
    }
}

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

public Handler getMessageHandler(String topic) {
    if (handlerMap == null) {
        loadClassesFromClassPath(); // This method should load from classpath all the classes with the annotation MessageHandler and build the handlerMap
    }
    return handlerMap.get(topic);
}

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

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