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


239

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

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

通常、他のIoCコンテナでは、Unity具体的な実装を登録してKey、それらを区別することができます。

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

or パラメータAddを取るServiceメソッドはありません。これは通常、具体的な実装を区別するために使用されます。keyname

    public void ConfigureServices(IServiceCollection services)
    {            
         // How do I register services of the same interface?            
    }


    public MyController:Controller
    {
       public void DoSomething(string key)
       { 
          // How do I resolve the service by key?
       }
    }

ここで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 CoreのデフォルトのDIには次の2つが欠けていると思います。

  1. キーを使用してインスタンスを登録する機能
  2. 登録時に静的データをコンストラクターに挿入する機能


2
名前ベースの登録のためのnugetの拡張がついに登場しました。それが役立つことを願っています
neleus

こんにちは、私の愚かな質問で申し訳ありませんが、私はMicrosoft.Extensions.DependencyInjectionの新機能についてです。「パブリックインターフェイスIServiceA:IService」のように、および「パブリッククラスServiceA:IServiceA」よりもIserviceを拡張する3つの空のインターフェイスを作成すると思いますか「...は良い実践オプションかもしれない?
Emiliano Magliocca 2017

この記事は役に立ちますか?stevejgordon.co.uk/…–
マイクB

Update1コンストラクタに物事を注入することは、どのオブジェクトを構築するかを決めることとは非常に異なるため、別の質問に移ることができます
Neil

回答:


246

私はFuncこの状況に陥ったときに使用する簡単な回避策を実行しました。

最初に共有デリゲートを宣言します。

public delegate IService ServiceResolver(string key);

次に、でStartup.cs、複数の具象登録とそれらのタイプの手動マッピングをセットアップします。

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

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

そして、DIに登録された任意のクラスからそれを使用します。

public class Consumer
{
    private readonly IService _aService;

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

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

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

ただし、通常はコードを腐敗させる巨大なnケースのスイッチが必要ないため、キーとして任意のカスタム解決タイプを使用できます。アプリのスケーリング方法によって異なります。


1
@MatthewStevenMonkanが私の回答を例で更新しました
Miguel A.

2
このようなファクトリパターンを使用するのが最善の方法です。共有してくれてありがとう!
セルゲイアコポフ2017

2
+1他のdiコンテナを使用する場合、依存関係を解決する必要があるときはいつでもパッケージを含める必要があるため、非常にすっきりとクリーン。AutoFacのILifetimeScope。
Anupam Singh

1
@AnupamSingh私の意見では、.NET Coreで実行されているほとんどの中小規模のアプリケーションは、DIフレームワークを必要とせず、複雑さと不要な依存関係を追加するだけで、組み込みDIの美しさとシンプルさは十分以上であり、簡単に拡張することもできます。
ミゲルA.アリラ

7
反対票の説明-それは非常に興味深いですが、私は現在、数年前に(MS DI革命の前に)誰かがこのFuncマジックをすべて削除するために大規模なコードベースをリファクタリングしています。さらに複雑なDI解像度が発生する可能性があります。たとえば、Windowsサービスハンドラーで作業したところ、Funcで実行するコードが1.6k行を超えていました。それを実行した後、DIの推奨される方法でそれを0.2k行に減らしました。OK-行のコードは何も意味しません..読みやすく、再利用しやすいことを除いて...
Piotr Kula

79

別のオプションは、拡張メソッドを使用することですGetServicesからMicrosoft.Extensions.DependencyInjection

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

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

次に、少量のLinqで解決します。

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

または

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

IService「名前」という文字列プロパティがあると仮定)

持っていることを確認してください using Microsoft.Extensions.DependencyInjection;

更新

AspNet 2.1ソース: GetServices


6
確かではありませんが、確定的ではないと思います。今日得られる結果は明日変わるかもしれません、それは良い習慣ではないようです。
rnrneverdies 2017

4
GetServicesのリンクに賛成投票してください。これにより、依存サービスであるサービスのリストをリクエストすることができますIEnumerable<IService>
johnny 5

20
serviceProvider.GetServices <IService>()は、ServiceA、ServiceB、ServiceCのそれぞれをインスタンス化します。1つのサービス(実際に必要なサービス)のみのコンストラクターを呼び出します。これは、実装が軽量でない場合、またはIServiceの実装が多数ある場合(たとえば、モデルごとにIRepositoryの実装が自動生成されている場合)は、大きな問題です。
ウロス2018

6
@Urosに同意します。これは良い解決策ではありません。10個のIService実装を登録し、実際に必要なインスタンスが最後のインスタンスである場合を想像してください。この場合、9つのインスタンスは実際にはDIによって作成され、決して使用されません。
thomai 2018

4
悪い考え:複数の未使用のインスタンス、サービスロケーターのアンチパターン、および実際の実装(typeof <ServiceA>)への直接結合。
Rico Suter、

20

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

ただし、次のような別の依存性注入メカニズムをプラグインできます。 StructureMap See it's Home pageGitHub Projectなど

それはまったく難しくありません:

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

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. 内部のASP.NETパイプラインに挿入し、ConfigureServicesクラスを登録します(ドキュメントを参照)

    public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider !
    {
        // Add framework services.
        services.AddMvc();
        services.AddWhatever();
    
        //using StructureMap;
        var container = new Container();
        container.Configure(config =>
        {
            // Register stuff in container, using the StructureMap APIs...
            config.For<IPet>().Add(new Cat("CatA")).Named("A");
            config.For<IPet>().Add(new Cat("CatB")).Named("B");
            config.For<IPet>().Use("A"); // Optionally set a default
            config.Populate(services);
        });
    
        return container.GetInstance<IServiceProvider>();
    }
  3. 次に、名前付きインスタンスを取得するには、 IContainer

    public class HomeController : Controller
    {
        public HomeController(IContainer injectedContainer)
        {
            var myPet = injectedContainer.GetInstance<IPet>("B");
            string name = myPet.Name; // Returns "CatB"

それでおしまい。

ビルドする例では、

    public interface IPet
    {
        string Name { get; set; }
    }

    public class Cat : IPet
    {
        public Cat(string name)
        {
            Name = name;
        }

        public string Name {get; set; }
    }

私はこのアプローチを試しましたが、ビルドプランにIContainerが見つからないため、コントローラーでランタイムエラーが発生します。IContainerに自動注入を要求するために必要なことはありますか?
mohrtan 2017年

ところで、私はStructureMap.Micorosoft.DependencyInjection 1.3.0を使用しています。
mohrtan 2017年

ConfigureServicesで新しいコンテナーを返していますか?
ヘラルドグリニョーリ2017年

上記の手順2で示したように、新しいコンテナのIServiceProviderInstanceを返しています。私はそれをコピーして、自分のタイプに合わせて変更するだけでした。これは優れたソリューションであり、完全に機能しています。唯一の欠点は、注入されたコンテナーを使用できず、静的コンテナーを使用していることです。
mohrtan 2017年

1
GerardoGrignoliに感謝します。@mohrtanまだ調べているのであれば、サンプルコードはここにあります。github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza

13

正解です。組み込みのASP.NET Coreコンテナーには、複数のサービスを登録してから特定のサービスを取得するという概念はありません。提案されているように、その場合の唯一の実際のソリューションはファクトリーです。

または、必要なソリューションを提供するUnityやStructureMapなどのサードパーティコンテナーに切り替えることもできます(ここに記載:https : //docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- the-default-services-container)。


13

同じ問題に直面しましたが、解決方法と理由を共有したいと思います。

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

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

では、どのようなオプションがありますか?人々は2つを提案します:

  • カスタムファクトリを使用する(など_myFactory.GetServiceByKey(key)

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

ここでFactoryパターンが唯一のオプションですか?

実際、各IoCコンテナーもファクトリーであるため、どちらのオプションもファクトリーです(ただし、高度に構成可能で複雑です)。また、他のオプションもファクトリーパターンのバリエーションであるように思えます。

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

まず、私は常に、新しい依存関係が本当に必要でない場合は、追加しないようにしています。だから私はこの点であなたに同意します。さらに、2つのDIフレームワークを使用することは、カスタムファクトリの抽象化を作成するよりも悪いです。2番目のケースでは、新しいパッケージの依存関係(Unityなど)を追加する必要がありますが、ここでは、新しいファクトリインターフェースに依存することはそれほど危険ではありません。ASP.NET Core DIの主なアイデアは、シンプルさだと思います。KISSの原則に従って、最小限の機能セットを維持します。追加の機能が必要な場合は、DIYまたは対応するプランジを使用してください目的の機能を実装をます(オープンクローズの原則)。

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

2番目の問題:

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

まず、私のような新しいものに依存していることをあなたに同意IOptions(したがって、パッケージ上のMicrosoft.Extensions.Options.ConfigurationExtensions)良いアイデアではありません。私はIOptions、その利点についてさまざまな意見があった場所について議論している人を見たことがあります。繰り返しになりますが、私は新しい依存関係が本当に必要ない場合は追加しないようにしています。本当に必要ですか?私は違うと思います。そうでなければ、各実装は、その実装から来る明確な必要なしにそれに依存する必要があります(私にとっては、私もあなたに同意するところ、ISPの違反のように見えます)。これは、工場に依存する場合にも当てはまりますが、この場合は、回避ます。

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

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

こんにちは、馬鹿げた質問で申し訳ありませんが、私はMicrosoft.Extensions.DependencyInjectionの初心者です...「パブリックインターフェイスIServiceA:IService」のように、「パブリッククラスServiceA:IServiceA」よりもIserviceを拡張する3つのインターフェイスを作成すると思いますか...は良い実践オプションかもしれませんか?
Emiliano Magliocca 2017

1
@ emiliano-magliocca一般に、IServiceAあなたの場合、使用しないインターフェース(ISP)に依存すべきではありません。からのメソッドIServiceのみを使用しているため、依存関係IServiceのみを持つ必要があります。
neleus 2017年

1
@ cagatay-kalan OPの質問の場合、彼はASP.NET Core DIで簡単に目標を達成できます。他のDIフレームワークは必要ありません。
-neleus

1
@EmilianoMaglioccaこれは、この方法で簡単に解決できます。services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));最初のクラスとservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));2番目のクラスです。
neleus

1
私の例の@EmilianoMaglioccaには、「MyFirstClass」と「MySecondClass」の両方に、EscposとUsbposの両方が実装するインターフェイスタイプの同じctorパラメータがあります。したがって、上記のコードはIoCコンテナに「MyFirstClass」と「MySecondClass」をインスタンス化する方法のみを指示します。これ以上何もない。そのため、さらに他のインターフェイスを「MyFirstClass」と「MySecondClass」にマップする必要がある場合があります。それはあなたのニーズに依存し、私の例ではそれをカバーしませんでした。
ネレウス

13

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

コントローラのDoSomething()メソッドでは、typeofを使用して必要なサービスを解決できます。var service = _services.FirstOrDefault(t => t.GetType()== typeof(ServiceA));
Ciaran Bruen

私は文字通りすべてを試しましたが、これが私にとってうまくいった唯一の解決策です。ありがとう!
Skatz1990

@ Skatz1990以下で作成したソリューションを別の投稿で試してください。私はそれがよりクリーンで使いやすいと思います。
Tブラウン

12

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

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

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

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


承認された回答スミレは、単一の責任原則ですか?
LP13

stackoverflow.com/a/52066039/876814のコメントを参照してください。また、承認された回答では、サービスは遅延して解決されます。つまり、実行時に失敗するかどうかだけがわかり、コンテナのビルド後に起動時にこれを静的にチェックする方法はありません(同様)コメントの答え)。SRPは、サービスがビジネスロジックだけでなく、依存関係の解決も担当するため
Rico Suter

@RicoSuter私はあなたのブログのソリューションが本当に好きですが、Startupクラス内のDIによって混乱しています。具体的には、MessagePublisher( "MyOrderCreatedQueue")という行を理解できません。そのシグネチャを持つコンストラクタが表示されないためです。services.AddSingleton <IMessagePublisher <OrderCreatedMessage >>(new MessagePublisher <OrderCreatedMessage>(new MessagePublisher( "MyOrderCreatedQueue")));
リーZ

ありがとう、記事を更新し、MyMessagePublisher
Rico Suter

7

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

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
IMyInterface<CustomerSavedConsumer>IMyInterface<ManagerSavedConsumer>異なるサービスタイプです-これはOPの質問にはまったく答えません。
Richard Hauer

2
OPは、Asp.netコアに同じインターフェイスの複数の実装を登録する方法を必要としていました。私がこれをしなかった場合、その方法を(正確に)説明してください。
グレー

1
あなたが正しい間、このパターンはオペレーションが望んだ効果を可能にします。少なくとも私がこれを自分でやろうとしたとき、私はこの投稿を偶然見つけました。私の解決策は自分の状況に最も適していました。
グレー

1
(MS DIを使用して)単一のインターフェースに複数の実装を登録しても、コンテナーが1つの実装と別の実装を区別できないという問題が多かったと思います。他のDIでは、それらにキーを設定して、コンテナがどちらを選択するかを認識できます。MSでは、あなたは持っているデリゲートを使用して手動で選択します。インターフェースが異なるため、ソリューションはこのシナリオに対応していません。そのため、コンテナーは適切な実装を選択する問題を抱えていません。サンプルは明らかに機能しますが、前述の問題の解決策ではありません。
Richard Hauer

3
@Grayあなたの投稿に悪い報道があったとしても、この解決策を提案してくれてありがとう。.netコアDIの制限を克服する別のオプションを読者に提供します。それはOPの質問に直接答えることはできませんが、SOがすべてについて正しい、完璧な代替ソリューションを提供しますよね?
Neil Watson

5

どうやら、あなたはあなたのサービスインターフェースのIEnumerableを単に注入することができます!次に、LINQを使用して必要なインスタンスを見つけます。

私の例はAWS SNSサービスの場合ですが、実際に注入されたサービスでも同じことができます。

起動

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}

services.AddSingleton<ISNSFactory, SNSFactory>();

services.Configure<SNSConfig>(Configuration);

SNSConfig

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",

SNSファクトリー

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;

    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }

    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }

    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }

    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }

    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);

        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }

        return service;
    }
}

public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();

    IAmazonSimpleNotificationService ForSMS();
}

これで、カスタムサービスまたはコントローラーで必要なリージョンのSNSサービスを取得できます

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;

    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }

    .......
 }

public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;

    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }

     .........
}

5

工場のアプローチは確かに実行可能です。別のアプローチは、継承を使用して、IServiceから継承する個々のインターフェースを作成し、IService実装に継承インターフェースを実装し、ベースではなく継承インターフェースを登録することです。継承階層を追加するか、ファクトリを追加するかが「正しい」パターンかどうかは、誰と話すかに依存します。次のようなジェネリックを使用する同じアプリケーションで複数のデータベースプロバイダーを扱う場合、このパターンを使用する必要があります。IRepository<T>データアクセスの基盤としてます。

インターフェースと実装の例:

public interface IService 
{
}

public interface IServiceA: IService
{}

public interface IServiceB: IService
{}

public IServiceC: IService
{}

public class ServiceA: IServiceA 
{}

public class ServiceB: IServiceB
{}

public class ServiceC: IServiceC
{}

コンテナ:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();

5

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

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

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

次に、辞書をservice-collectionに登録します。

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

その後、辞書を取得してキーでアクセスしたくない場合は、サービスコレクションに追加のキー検索メソッドを追加して、辞書を非表示にすることができます
(デリゲート/クロージャーを使用すると、メンテナーに見込みが与えられます。何が起こっているのかを理解する-矢印表記は少し不可解です)

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

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

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

今、あなたはどちらかであなたのタイプにアクセスできます

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

または

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

ご覧のとおり、最初のものは完全に不要です。これは、クロージャとAddTransientを必要とせずに、ディクショナリを使用して正確に実行できるためです(VBを使用する場合、中かっこも異なることはありません)。

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

(シンプルな方が良いですが、拡張メソッドとして使用することもできます)

もちろん、辞書が気に入らない場合は、インターフェースにプロパティName(または何でも)を装備し、キーで検索することもできます。

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



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

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

                return null;
            };
    });

ただし、そのためには、プロパティに対応するようにインターフェイスを変更する必要があり、多くの要素のループ処理は、連想配列ルックアップ(辞書)よりもはるかに遅くなるはずです。
とはいえ、それが辞書なしでできることを知っているのは良いことです。

これらはちょうど私の0.05ドルです


サービスがIDispose実装されている場合、サービスの廃棄は誰が担当しますか?次のように辞書を登録しましたSingleton
LP13

@ LP13:デリゲートを値として辞書を登録し、itransientに登録して、新しいインスタンスを作成することもできます。GetRequiredService <T>()["logDB"]()
Stefan Steiger

5

上記の投稿以来、ジェネリックファクトリークラスに移動しました

使用法

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

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

実装

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

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

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

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

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

拡張

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

.AddFactory()メソッドの拡張を提供できますか?
開発者

申し訳ありませんが、これを見ました...追加
Tブラウン

3

@Miguel A. Arillaがそれを明確に指摘し、私が彼に投票したようですが、私は彼の便利なソリューションに加えて、見栄えがよいがさらに多くの作業を必要とする別のソリューションを作成しました。

それは間違いなく上記のソリューションに依存します。だから基本的に私はに似たものを作成し、それをインターフェイスとしてFunc<string, IService>>呼び出しIServiceAccessor、次にいくつかの拡張をそのIServiceCollectionように追加する必要がありました:

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

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

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

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

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

    }

結果として、他のコンテナーで行ったように、名前または名前付きインスタンスでサービスを登録できるようになります。たとえば、次のようになります。

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

現時点ではこれで十分ですが、作業を完了するには、同じ方法ですべてのタイプの登録をカバーできるように、拡張メソッドを追加することをお勧めします。

stackoverflowに関する別の投稿がありましたが、私はそれを見つけることができません。投稿者がこの機能がサポートされていない理由とその回避方法を詳細に説明しているため、基本的に@Miguelが述べたのと同様です。名前付きインスタンスが本当に必要な状況があると思うので、私は各点に同意しませんが、それは素晴らしい投稿でした。もう一度見つけたら、ここにそのリンクを投稿します。

実際には、そのセレクターまたはアクセサーを渡す必要はありません。

私のプロジェクトでは次のコードを使用していますが、これまでのところうまくいきました。

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

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

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

    /// <summary>
    /// Registers an interface for a class
    /// </summary>
    /// <typeparam name="TInterface">The type of the t interface.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection As<TInterface>(this IServiceCollection services)
         where TInterface : class
    {
        var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
        if (descriptor.IsNotNull())
        {
            var provider = services.BuildServiceProvider();
            var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
            services?.AddSingleton(implementationInstance);
        }
        return services;
    }

1
これにより、サービスアクセサーの型の登録が失われるという問題の解決に役立ちました。トリックは、サービスアクセサーのすべてのバインディングを削除してから、もう一度追加することでした。
Umar Farooq Khawaja

3

私はいくつかの素晴らしい機能を実装するためのライブラリを作成しました。コードはGitHubにあります:https : //github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https

使い方は簡単です:

  1. Dazinator.Extensions.DependencyInjection nugetパッケージをプロジェクトに追加します。
  2. 名前付きサービスの登録を追加します。
    var services = new ServiceCollection();
    services.AddNamed<AnimalService>(names =>
    {
        names.AddSingleton("A"); // will resolve to a singleton instance of AnimalService
        names.AddSingleton<BearService>("B"); // will resolve to a singleton instance of BearService (which derives from AnimalService)
        names.AddSingleton("C", new BearService()); will resolve to singleton instance provided yourself.
        names.AddSingleton("D", new DisposableTigerService(), registrationOwnsInstance = true); // will resolve to singleton instance provided yourself, but will be disposed for you (if it implements IDisposable) when this registry is disposed (also a singleton).

        names.AddTransient("E"); // new AnimalService() every time..
        names.AddTransient<LionService>("F"); // new LionService() every time..

        names.AddScoped("G");  // scoped AnimalService
        names.AddScoped<DisposableTigerService>("H");  scoped DisposableTigerService and as it implements IDisposable, will be disposed of when scope is disposed of.

    });

上記の例では、名前付き登録ごとに、ライフタイムまたはシングルトン、スコープ、または一時を指定していることに注意してください。

サービスをこのパッケージに依存させないことに慣れているかどうかに応じて、次の2つの方法のいずれかでサービスを解決できます。

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

または

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

私はこのライブラリをMicrosoft.Extensions.DependencyInjectionで適切に機能するように特別に設計しました-たとえば:

  1. あなたが名前のサービスを登録すると、あなたが登録することを任意のタイプは、パラメータを持つコンストラクタを持つことができます-彼らは同じように、DI経由で満足されAddTransient<>AddScoped<>及びAddSingleton<>方法は、通常動作します。

  2. 一時的でスコープ付きの名前付きサービスの場合、レジストリはを構築して、ObjectFactory必要なときにそのタイプの新しいインスタンスを非常に迅速にアクティブ化できるようにします。これは他のアプローチよりもはるかに高速で、Microsoft.Extensions.DependencyInjectionがどのように実行するかと一致しています。


2

それが価値があるものに対する私の解決策...キャッスルウィンザーへの切り替えを検討しました。ごめんなさい!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

さまざまな実装を作成する

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

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

登録

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

コンストラクターとインスタンスの使用法...

public class Whatever
{
   private IStage ClassA { get; }

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

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

1

@rnrneverdiesのソリューションを拡張する。ToString()の代わりに、次のオプションを使用することもできます。1)共通のプロパティ実装で、2)@Craig Brunettiによって提案されたサービスのサービス。

public interface IService { }
public class ServiceA : IService
{
    public override string ToString()
    {
        return "A";
    }
}

public class ServiceB : IService
{
    public override string ToString()
    {
        return "B";
    }

}

/// <summary>
/// extension method that compares with ToString value of an object and returns an object if found
/// </summary>
public static class ServiceProviderServiceExtensions
{
    public static T GetService<T>(this IServiceProvider provider, string identifier)
    {
        var services = provider.GetServices<T>();
        var service = services.FirstOrDefault(o => o.ToString() == identifier);
        return service;
    }
}

public void ConfigureServices(IServiceCollection services)
{
    //Initials configurations....

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

    var sp = services.BuildServiceProvider();
    var a = sp.GetService<IService>("A"); //returns instance of ServiceA
    var b = sp.GetService<IService>("B"); //returns instance of ServiceB

    //Remaining configurations....
}

1

ここでの回答と他の記事を読んだ後、文字列なしで動作させることができました。同じインターフェースの複数の実装がある場合、DIはこれらをコレクションに追加するので、を使用してコレクションから必要なバージョンを取得できますtypeof

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(IService, ServiceA);
    services.AddScoped(IService, ServiceB);
    services.AddScoped(IService, ServiceC);
}

// Any class that uses the service(s)
public class Consumer
{
    private readonly IEnumerable<IService> _myServices;

    public Consumer(IEnumerable<IService> myServices)
    {
        _myServices = myServices;
    }

    public UseServiceA()
    {
        var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
        serviceA.DoTheThing();
    }

    public UseServiceB()
    {
        var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
        serviceB.DoTheThing();
    }

    public UseServiceC()
    {
        var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
        serviceC.DoTheThing();
    }
}

IoCの目的を無効にします。あなたにもちょうど書くかもしれません:var serviceA = new ServiceA();
ジェームズ・カラン

2
@JamesCurranは、ServiceAに依存関係がある場合、またはクラスのユニットテストを行う場合には必要ありません。
Jorn.Beyers

0

すぐに使える実装では提供されていませんが、名前付きインスタンスを登録し、コードにINamedServiceFactoryを注入して名前でインスタンスを取得できるようにするサンプルプロジェクトを次に示します。ここの他のfacoryソリューションとは異なり、同じ実装の複数のインスタンスを登録できますが、構成が異なります

https://github.com/macsux/DotNetDINamedInstances


0

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

INamedServiceインターフェイス(.Nameプロパティ付き)がある場合、.GetService(string name)のIServiceCollection拡張機能を記述できます。拡張機能はその文字列パラメーターを受け取り、それ自体に.GetServices()を実行し、インスタンスで、INamedService.Nameが指定された名前と一致するインスタンスを見つけます。

このような:

public interface INamedService
{
    string Name { get; }
}

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

したがって、IMyServiceはINamedServiceを実装する必要がありますが、必要なキーベースの解決が得られますよね?

公平を期すために、このINamedServiceインターフェースを用意する必要があることは醜いように見えますが、さらに進んで物事をよりエレガントにしたい場合、実装/クラスの[NamedServiceAttribute( "A")]はこのコードで見つけることができます拡張機能、それも同様に動作します。さらに公平に言うと、Reflectionは遅いため、最適化が必要な場合がありますが、正直なところ、これはDIエンジンが支援してきたはずです。スピードとシンプルさは、それぞれTCOに大きく貢献しています。

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


2
これはサービスロケーターパターンと呼ばれ、絶対に必要な場合を除いて、通常は最適なルートではありません
Joe Phillips

@JoePhillipsなぜそれが良い解決策ではないのかについて何か意見がありますか?私はそれの優雅さが大好きです。私が考えることができる唯一の欠点は、取得するたびにそれらすべてのインスタンスを作成することです。
Peter

2
@ピーター主な理由は、作業が非常に難しいためです。serviceLocatorオブジェクトをクラスに渡す場合、そのクラスが使用する依存関係は、魔法の「神」オブジェクトからすべてを取得するため、まったく明らかではありません。変更するタイプの参照を見つける必要があると想像してください。サービスロケータオブジェクトを介してすべてを取得しているとき、その機能は基本的に消えます。コンストラクターインジェクションの方がはるかに明確で信頼性が高い
Joe Phillips

私は知らないよ。自明性は私にとってマイナスではありません...私のコンポーネントが依存関係をどのように活用するかを追跡することを気にかければ、そのための単体テストがあるでしょう...各依存関係を参照するだけでなく、理解に役立つテスト各依存関係がどのように必要か。コンストラクタを読むことで、他にどのようにそれを認識しますか?
Craig Brunetti、

0

同じ問題に遭遇し、名前付きサービスを許可する単純な拡張機能を使用しました。あなたはそれをここで見つけることができます:

これにより、次のように必要なだけ(名前付き)サービスを追加できます。

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

 var serviceProvider = serviceCollection.BuildServiceProvider();

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

ライブラリを使用すると、次のような「ファクトリパターン」を簡単に実装できます。

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

        serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

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

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

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

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

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

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

それが役に立てば幸い


0

IServiceCollection使用したWithName拡張機能の上に独自の拡張機能を作成しました。

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

ServiceCollectionTypeMapperマップするシングルトンインスタンスであるIService> NameOfService> Implementationインターフェースは異なる名前を持つ多くの実装を持っている可能性があり、これは我々がおしっこの必要性を解決することができるよりも種類を登録することができますし、我々が望むものを選択するために決意複数のサービスとは異なるアプローチです。

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

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

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

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

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

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

新しいサービスを登録するには:

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

サービスを解決するには、IServiceProviderこのような拡張が必要です。

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

解決する場合:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

serviceProviderは、アプリケーションのコンストラクタ内に次のように注入できることに注意してください IServiceProvider

これがお役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.