web-dev-qa-db-ja.com

Asp.Netコアに同じインターフェースの複数の実装を登録する方法

同じインターフェースから派生したサービスがあります

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

通常Unityのような他のIOCコンテナを使うと、それらを区別するいくつかの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()メソッドを呼び出すときに、コンストラクタにデータを挿入できません。例えば、この例を考えてください

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に対するものです 詳細はこちら

だから私はASP.NETコアのデフォルトのDIは2つのことを欠いていると思う
1>キーを使用してインスタンスを登録する
2>登録中に静的データをコンストラクタに注入する

147
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)
    {
        this.serviceAccessor = serviceAccesor;
    }

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

UPDATE

この例では、簡単にするために、またOPが特にこのケースを求めているために、解決のためのキーは文字列であることに注意してください。

しかし、通常は巨大なnケーススイッチでコードを壊す必要はないため、任意のカスタム解像度タイプをキーとして使用できます。アプリの規模に応じて異なります。

138

別の選択肢はMicrosoft.Extensions.DependencyInjectionからの拡張メソッド GetServices を使うことです。

あなたのサービスを次のように登録します。

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

58
rnrneverdies

Microsoft.Extensions.DependencyInjectionではサポートされていません。

しかし、StructureMapのような別の依存性注入メカニズムをプラグインすることもできます。Home page および GitHub Project を参照してください。

まったく難しいことではありません。

  1. project.jsonでStructureMapに依存関係を追加します。

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
    
  2. それをConfigureServices内のASP.NETパイプラインに注入し、クラスを登録します (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>();
    }
    
  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

私は同じ問題に直面していて、私がそれをどのように解決したか、そしてその理由を共有したいと思います。

あなたが述べたように、2つの問題があります。 最初:

Asp.Net Coreでこれらのサービスを登録し、実行時に何らかのキーに基づいて解決するにはどうすればよいですか?

それでは、どのような選択肢がありますか?人々は2つを示唆している:

  • カスタムファクトリを使用する(_myFactory.GetServiceByKey(key)のように)

  • 別のDIエンジンを使用する(_unityContainer.Resolve<IService>(key)のように)

ここではFactoryパターンが唯一の選択肢ですか?

各IoCコンテナもファクトリであるため、実際には両方のオプションが工場です(ただし、高度に設定可能で複雑です)。そして、私には他のオプションもFactoryパターンのバリエーションであるように思われます。

それでは、どのオプションがそれより優れているのでしょうか。ここで私はカスタムファクトリの使用を提案した@Sockに同意します、そしてそれがその理由です。

第一に、私は新しい依存関係が本当に必要とされていないときにそれらを追加することを避けるようにしています。だから私はこの点であなたと同意します。さらに、2つのDIフレームワークを使用することは、カスタムファクトリ抽象化を作成するよりも悪いです。 2番目のケースでは(Unityのように)新しいパッケージの依存関係を追加する必要がありますが、新しいファクトリインターフェースに依存することはここではそれほど悪ではありません。私は、ASP.NET Core DIの主な考え方は単純さだと思います。それは KISSの原則 に従って最小限の機能を維持します。追加の機能が必要な場合は、DIYするか、目的の機能を実装する対応する Plungin を使用してください(Open Closed Principle)。

次に、多くの場合、単一のサービスに対して多くの名前付きの依存関係を注入する必要があります。 Unityの場合、コンストラクタパラメータの名前を指定する必要があるかもしれません(InjectionConstructorを使用)。この登録はリフレクタとスマートロジックを使ってコンストラクタの引数を推測します。登録がコンストラクタの引数と一致しない場合、これもランタイムエラーにつながる可能性があります。一方、自分のファクトリを使用するときは、コンストラクタパラメータを提供する方法を完全に制御できます。読みやすく、コンパイル時に解決されます。 またKISSの原理

第二の問題:

_serviceProvider.GetService()はどのようにして適切な接続文字列を挿入できますか?

まず、IOptionsのような(そしてそれゆえにパッケージMicrosoft.Extensions.Options.ConfigurationExtensionsのような)新しいことに頼るのは良い考えではないということにあなたは同意します。私は、その利益について異なる意見があるIOptionsについて議論している人を見ました。繰り返しになりますが、私は新しい依存関係が本当に必要でないときにそれらを追加することを避けようとします。本当に必要ですか?違うと思う。そうでなければ、それぞれの実装は、その実装からはっきりとした必要性なしにそれに依存しなければならないでしょう(私にとっては、私はあなたにも同意するISPの違反のように見えます)。これはファクトリに依存することについても当てはまりますが、この場合回避することができます

ASP.NET Core DIは、その目的のために非常に優れたオーバーロードを提供します。

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

そうです、内蔵ASP.NET Coreコンテナには、複数のサービスを登録してから特定のサービスを取得するという概念はありません。お勧めのとおり、ファクトリが唯一の現実的なソリューションです。

あるいは、必要なソリューションを提供するUnityやStructureMapのようなサードパーティ製のコンテナに切り替えることもできます(ここに文書化されている: https://docs.asp.net/ja/latest/fundamentals/dependency-injection)。 html?#デフォルトサービスコンテナの置き換え ).

10
Sock

単にIEnumerableを注入するだけです

Startup.csのConfigureServices

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;
    }
6
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ファクトリー

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

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

サービスアクセサは次のようになります。

 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についての別の投稿がありましたが、私はそれを見つけることができません、投稿者がこの機能がサポートされていない理由とその回避方法について詳細に説明しました。私はあなたが本当に名前付きインスタンスを必要とする状況があると思うので私は各点に同意しないけれどもそれは素晴らしい投稿でした。また見つけたら、このリンクをここに掲載します。

実際のところ、そのSelectorや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;
    }
2
Assil

このパーティーには少し遅れましたが、これが私の解決策です:...

汎用ハンドラの場合はStartup.csまたはProgram.cs ...

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

Tインタフェース設定のIMyInterface

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

TのIMyInterfaceの具体的な実装

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

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

うまくいけば、この方法で問題が発生した場合、誰かがこれがこれを行うのが間違った方法である理由を親切に指摘します。

2
Gray

ネクロマンシング.
私はここの人々が車輪を再発明していると思います。
キーでコンポーネントを登録したい場合は、辞書を使用してください。

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

そして辞書をservice-collectionに登録します。

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

辞書を取得してそれをキーでアクセスしたくない場合は、service-collectionにkey-lookup-methodを追加して辞書を非表示にすることができます。
(デリゲート/クロージャーの使用は将来のメンテナに何が起こっているのかを理解する機会を与えるべきです - 矢印表記は少しわかりにくいです)

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を必要とせずに辞書を使用して正確に行うことができるため、最初のものはまったく不要です。

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ドルです

1
Stefan Steiger

それが価値があるもののための私の解決策は、私が上記の解決策のどれも好きであると言うことができないように城ウィンザーに切り替えることを考えました。ごめんなさい!!

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

ファクトリアプローチは確かに実行可能です。もう1つの方法は、継承を使用して、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>();
1
jlc397

ここでの回答のほとんどは、単一の責任原則(サービスクラスが依存関係自体を解決するべきではない)に違反するか、サービスロケーターアンチパターンを使用します。

これらの問題を回避する別のオプションは次のとおりです。

  • インターフェイスで追加のジェネリック型パラメーターを使用するか、非ジェネリックインターフェイスを実装する新しいインターフェイスを使用します。
  • アダプタ/インターセプタークラスを実装してマーカータイプを追加し、
  • 「名前」として汎用タイプを使用します

詳細な記事を書きました: 。NETでの依存性注入:欠落している名前付き登録を回避する方法

1
Rico Suter

@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....
}
0
vrluckyin

すぐに使用できる実装では提供されていませんが、ここで名前付きインスタンスを登録し、コードにINamedServiceFactoryを挿入して名前でインスタンスを取り出すことができるサンプルプロジェクトです。ここでの他の面倒な解決策とは異なり、それはあなたが同じ実装の複数のインスタンスを登録することを可能にしますが異なって設定されます

https://github.com/macsux/DotNetDINamedInstances

0
Andrew Stakhov

サービスのサービスはどうですか?

(.Nameプロパティを持つ)INamedServiceインターフェイスがある場合は、.GetService(string name)用のIServiceCollection拡張を作成できます。この拡張は、その文字列パラメータを受け取り、それ自体で.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エンジンが助けているべきだったものです。スピードとシンプルさがTCOの大きな貢献をしています。

全体として、「名前付きサービスの検索」は再利用可能な概念であり、ファクトリクラスはソリューションとして拡張できないため、明示的なファクトリは必要ありません。そしてFunc <>は問題ないように見えますが、switchブロックはとてもblehです。そしてまた、あなたがすることと同じくらい頻繁にFuncを書くでしょう。工場を書く。より少ないコードで、単純で再利用可能なものから始めてください。それが、yaのためにそれをしないのであれば、複雑になります。

0
Craig Brunetti