it-swarm.com.ru

Как зарегистрировать несколько реализаций одного и того же интерфейса в 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 принимал параметр 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
       }
    }

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

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

public class ServiceA : IService
{
     private string _efConnectionString;
     ServiceA(string efconnectionString)
     {
       _efConnecttionString = efConnectionString;
     } 
}

public class ServiceB : IService
{    
   private string _mongoConnectionString;
   public ServiceB(string mongoConnectionString)
   {
      _mongoConnectionString = mongoConnectionString;
   }
}

public class ServiceC : IService
{    
    private string _someOtherConnectionString
    public ServiceC(string someOtherConnectionString)
    {
      _someOtherConnectionString = someOtherConnectionString;
    }
}

Как _serviceProvider.GetService() может ввести соответствующую строку подключения? В Unity или любом другом IOC мы можем сделать это во время регистрации типа. Я могу использовать IOption , однако для этого потребуется ввести все параметры, я не могу добавить конкретную строку подключения в службу.

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

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

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

124
LP13

Я сделал простой обходной путь, используя 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)
    {
        _serviceAccessor = serviceAccesor;
    }

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

ОБНОВЛЕНИЕ

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

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

110
Miguel A. Arilla

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

Убедитесь, что у вас есть using Microsoft.Extensions.DependencyInjection;

Обновление

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

46
rnrneverdies

Это не поддерживается 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; }
    }
15
Gerardo Grignoli

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

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

10
Sock

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я просто ввожу 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;
    }
4
T Brown

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

     .........
}
3
ArcadeRenegade

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

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

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

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

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

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

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

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

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

public class Whatever
{
   private IStage ClassA { get; }

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

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

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

0
Stefan Steiger

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

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

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

IMyInterface T Interface Setup

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

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

0
Gray

Фабричный подход, безусловно, жизнеспособен. Другой подход заключается в использовании наследования для создания отдельных интерфейсов, которые наследуются от 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>();
0
jlc397

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

https://github.com/macsux/DotNetDINamedInstances

0
Andrew Stakhov

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

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

0
Craig Brunetti