web-dev-qa-db-ja.com

名前で解決する依存性注入

特定のクラスのオブジェクトの異なる実装を注入するにはどうすればよいですか?

たとえば、1の場合、次のことができます。IRepositoryの2つの実装を定義する

container.RegisterType<IRepository, TestSuiteRepositor("TestSuiteRepository");
container.RegisterType<IRepository, BaseRepository>(); 

必要な実装を呼び出す

public BaselineManager([Dependency("TestSuiteRepository")]IRepository repository)
23
Ilya

@Tsengが指摘したように、名前付きバインディングの組み込みソリューションはありません。ただし、ファクトリメソッドを使用すると、ケースに役立つ場合があります。例は次のようになります。

リポジトリリゾルバを作成します。

public interface IRepositoryResolver
{
    IRepository GetRepositoryByName(string name);
}

public class RepositoryResolver : IRepositoryResolver 
{
    private readonly IServiceProvider _serviceProvider;
    public RepositoryResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    public IRepository GetRepositoryByName(string name)
    {
         if(name == "TestSuiteRepository") 
           return _serviceProvider.GetService<TestSuiteRepositor>();
         //... other condition
         else
           return _serviceProvider.GetService<BaseRepositor>();
    }

}

必要なサービスをConfigureServices.csに登録します

services.AddSingleton<IRepositoryResolver, RepositoryResolver>();
services.AddTransient<TestSuiteRepository>();
services.AddTransient<BaseRepository>(); 

最後に、それを任意のクラスで使用します。

public class BaselineManager
{
    private readonly IRepository _repository;

    public BaselineManager(IRepositoryResolver repositoryResolver)
    {
        _repository = repositoryResolver.GetRepositoryByName("TestSuiteRepository");
    }
}
23
adem caglin

@ adem-caglinの回答に加えて、名前ベースの登録用に作成した再利用可能なコードをここに投稿します。

[〜#〜] update [〜#〜]これで、 nugetパッケージ として利用できます。

サービスを登録するには、Startupクラスに次のコードを追加する必要があります。

        services.AddTransient<ServiceA>();
        services.AddTransient<ServiceB>();
        services.AddTransient<ServiceC>();
        services.AddByName<IService>()
            .Add<ServiceA>("key1")
            .Add<ServiceB>("key2")
            .Add<ServiceC>("key3")
            .Build();

次に、IServiceByNameFactoryインターフェースを介して使用できます。

public AccountController(IServiceByNameFactory<IService> factory) {
    _service = factory.GetByName("key2");
}

または、ファクトリー登録を使用してクライアント・コードをクリーンに保つことができます(私が好む)

_container.AddScoped<AccountController>(s => new AccountController(s.GetByName<IService>("key2")));

拡張機能の完全なコードは github にあります。

22
neleus

組み込みのASP.NET Core IoCコンテナーではできません。

これは設計によるです。組み込みコンテナは意図的にシンプルかつ簡単に拡張できるように維持されているため、さらに機能が必要な場合はサードパーティのコンテナをプラグインできます。

これを行うには、Autofacなどのサードパーティのコンテナを使用する必要があります( docs を参照)。

public BaselineManager([WithKey("TestSuiteRepository")]IRepository repository)
6
Tseng

依存関係注入の公式ドキュメント を読んだ後は、この方法でそれを行うことはできないと思います。

しかし、私が持っている質問は、これらの2つの実装が同時に必要ですか?そうしないと、 環境変数を使用して複数の環境を作成する にして 現在の環境に基づいてStartupクラスの特定の機能 を使用できるため、または複数のStartup{EnvironmentName}クラスを作成します。

ASP.NET Coreアプリケーションが起動すると、Startupクラスがbootstrapアプリケーションのロード、構成設定の読み込みなどに使用されます(ASP.NET起動の詳細)。ただし、Startup{EnvironmentName}という名前のクラスが存在し(たとえば、StartupDevelopment)、ASPNETCORE_ENVIRONMENT環境変数がその名前と一致する場合は、代わりにそのStartupクラスが使用されます。 、開発用にStartupを構成することもできますが、アプリが本番環境で実行されるときに使用される個別のStartupProductionを使用することも、その逆も可能です。

私も JSONファイルからの依存関係の注入に関する記事を書きました なので、実装を切り替えるたびにアプリケーション全体を再コンパイルする必要はありません。基本的に、次のようなサービスでJSON配列を保持します。

"services": [
    {
        "serviceType": "ITest",
        "implementationType": "Test",
        "lifetime": "Transient"
    }
]

次に、このファイルで目的の実装を変更できます。環境変数を再コンパイルまたは変更する必要はありません。

お役に立てれば!

4
radu-matei

まず、これはおそらく悪い考えです。達成しようとしているのは、依存関係の使用方法と依存関係の定義方法の分離です。しかし、依存性注入フレームワークに反対するのではなく、それを使用したいと考えています。サービスロケーターのアンチパターンの不十分な発見能力の回避。 ILogger<T>/IOptions<T>と同様の方法でジェネリックを使用しないのはなぜですか?

public BaselineManager(RepositoryMapping<BaselineManager> repository){
   _repository = repository.Repository;
}

public class RepositoryMapping<T>{
    private IServiceProvider _provider;
    private Type _implementationType;
    public RepositoryMapping(IServiceProvider provider, Type implementationType){
        _provider = provider;
        _implementationType = implementationType;
    }
    public IRepository Repository => (IRepository)_provider.GetService(_implementationType);
}

public static IServiceCollection MapRepository<T,R>(this IServiceCollection services) where R : IRepository =>
    services.AddTransient(p => new RepositoryMapping<T>(p, typeof(R)));

services.AddScoped<BaselineManager>();
services.MapRepository<BaselineManager, BaseRepository>();

.netコア3以降、マッピングの定義に失敗した場合、検証エラーが発生するはずです。

1
Jeremy Lakeman