現在、ASP.NET Core プロジェクトに取り組んでおり、組み込みの依存性注入 (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
ベストアンサー1
あなたが探している機能は、少なくともコントローラーで使用する場合は、コントローラーが少し特別に扱われているため、実装するのは簡単ではありません(デフォルトでは、コントローラーは登録されていないServiceCollection
ため、コンテナーによって解決/インスタンス化されず、代わりにリクエスト中にASP.NET Coreによってインスタンス化されます。私の関連する回答)。
組み込みの 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. 登録を変更する ドキュメントに説明があるここコードを変更したくない、または変更できない場合は、
// 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. キー付きサービスの使用(ここ) これは2.の以前のアプローチと非常に似ていますが、具体的なタイプではなくキーに基づいてサービスを解決します。
##4. メタデータを使用する これは 3. と非常に似ていますが、属性を介してキーを定義します。
その他のコンテナ団結特別な属性があり、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;
}
}