web-dev-qa-db-ja.com

依存性注入による複数の実装の注入

現在、ASP.NET Core Projectに取り組んでおり、組み込みのDependency Injection(DI)機能を使用したいと考えています。

さて、私はインターフェイスから始めました:

_ICar
{
    string Drive();
}
_

ICarインターフェースを複数回実装したい

_public class BMW : ICar
{
    public string Drive(){...};
}

public class Jaguar : ICar
{
    public string Drive(){...};
}
_

Startupクラスに以下を追加します

_public void ConfigureServices(IServiceCollection services)
{
     // Add framework services.
     services.AddMvc();
     services.AddTransient<ICar, BMW>(); 
     // or 
     services.AddTransient<ICar, Jaguar>();
 }
_

ここで、2つの実装の間で決定を行う必要があり、決定したクラスはICar実装を必要とするすべてのコンストラクターに設定されます。 しかし、私の考えは、要求されたコントローラーがBMWControllerである場合、BMW実装を使用するか、JaguarControllerが要求されている場合はJaguarを使用することでした

そうでなければ、DIは私には意味がありません。この問題を適切に処理するにはどうすればよいですか?

私の問題をよりよく理解するには、次の写真をご覧ください: https://media-www-asp.azureedge.net/media/44907/dependency-injection-golf.png ?raw = true 依存関係リゾルバーはどのように機能し、ASP.NET Coreでどこに設定できますか?

Unityでは、次のようなものを作成できますcontainer.RegisterType<IPerson, Male>("Male");container.RegisterType<IPerson, Female>("Female");

そして、このような正しいタイプを呼び出します

_[Dependency("Male")]IPerson malePerson
_
13

コントローラーは少し特別に扱われるため、少なくともコントローラーで使用している場合、探している機能を実装するのは簡単ではありません(デフォルトでは、コントローラーはServiceCollectionに登録されず、したがって解決されません/ instantiatedはコンテナによって、代わりにリクエスト中にASP.NET Coreによってインスタンス化されます。 my related answer )の説明と例を参照してください。

組み込みのIoCコンテナでは、ファクトリメソッドを介してのみ実行できます。ここでは、BmwCarFactoryクラスの例を示します。

services.AddScoped<ICar, BmwCar>();
services.AddScoped<BmwCar>();
services.AddScoped<BmwCarFactory>(p => new BmwCarFactory(p.GetRequiredService<BmwCar>())));

デフォルトのIoCコンテナーは、依存関係の注入の基本を提供するために意図的にシンプルに保たれ、他のIoCコンテナーが簡単にプラグインしてデフォルトの実装を置き換えることができるようにします。

より高度なシナリオでは、より高度な機能(アセンブリスキャン、デコレータ、条件付き/パラメータ化された依存関係など)をサポートするIoCを選択することをお勧めします。

AutoFac(プロジェクトで使用)は、このような高度なシナリオをサポートしています。 AutoFacのドキュメントには、4つのシナリオがあります(コメントで@pwasが提案した3番目のシナリオと合わせて)。

1.クラスを再設計する

コードおよびクラス階層をリファクタリングするための追加のオーバーヘッドが必要ですが、注入されたサービスの消費を大幅に簡素化します

2.登録を変更する

ドキュメントでは、コードを変更したくない、または変更できない場合、それについて説明します here

// Attach resolved parameters to override Autofac's
// lookup just on the ISender parameters.
builder.RegisterType<ShippingProcessor>()
       .WithParameter(
         new ResolvedParameter(
           (pi, ctx) => pi.ParameterType == typeof(ISender),
           (pi, ctx) => ctx.Resolve<PostalServiceSender>()));
builder.RegisterType<CustomerNotifier>();
       .WithParameter(
         new ResolvedParameter(
           (pi, ctx) => pi.ParameterType == typeof(ISender),
           (pi, ctx) => ctx.Resolve<EmailNotifier>()));
var container = builder.Build();

3.キー付きサービスの使用( here

前の2へのアプローチとかなり似ていますが、具体的なタイプではなく、キーに基づいてサービスを解決します

4.メタデータを使用する

これは3と非常に似ていますが、属性を介してキーを定義します。

nity などの他のコンテナには、DependencyAttributeなどの特別な属性があり、これを使用して依存関係に注釈を付けることができます。

public class BmwController : Controller
{
    public BmwController([Dependency("Bmw")ICar car)
    {
    }
}

ただし、これとAutofacの4番目のオプションにより、IoCコンテナーがサービスにリークするため、他のアプローチを検討する必要があります。

または、いくつかの規則に基づいてサービスを解決するクラスとファクトリーを作成します。たとえば、ICarFactory

public ICarFactory
{
    ICar Create(string carType);
}

public CarFactory : ICarFactory
{
    public IServiceProvider provider;

    public CarFactory(IServiceProvider provider)
    {
        this.provider = provider;
    }

    public ICar Create(string carType)
    {
        if(type==null)
            throw new ArgumentNullException(nameof(carType));

        var fullQualifedName = $"MyProject.Business.Models.Cars.{carType}Car";
        Type carType = Type.GetType(fullQualifedName);
        if(carType==null)
            throw new InvalidOperationException($"'{carType}' is not a valid car type.");

        ICar car = provider.GetService(carType);
        if(car==null)
            throw new InvalidOperationException($"Can't resolve '{carType.Fullname}'. Make sure it's registered with the IoC container.");

        return car;
    }
}

そして、それを次のように使用します

public class BmwController : Controller
{
    public ICarFactory carFactory;

    public BmwController(ICarFactory carFactory)
    {
        this.carFactory = carFactory;

        // Get the car
        ICar bmw = carFactory.Create("Bmw");
    }
}

IServiceProviderの代替

// alternatively inject IEnumerable<ICar>
public CarFactory : ICarFactory
{
    public IEnumerable<ICar> cars;

    public CarFactory(IEnumerable<ICar> cars)
    {
        this.cars = cars;
    }

    public ICar Create(string carType)
    {
        if(type==null)
            throw new ArgumentNullException(nameof(carType));

        var carName = ${carType}Car";
        var car = cars.Where(c => c.GetType().Name == carName).SingleOrDefault();

        if(car==null)
            throw new InvalidOperationException($"Can't resolve '{carName}.'. Make sure it's registered with the IoC container.");

        return car;
    }
}
19
Tseng