web-dev-qa-db-ja.com

渡されたサービスに基づいてインターフェースを解決する方法

私はインターフェースを持っています。

public interface ISomeInterface {...}

および2つの実装(SomeImpl1およびSomeImpl2):

public class SomeImpl1 : ISomeInterface {...}
public class SomeImpl2 : ISomeInterface {...}

また、ISomeInterfaceを(コンストラクターを介して)注入する2つのサービスがあります。

public class Service1 : IService1 
{
   public Service1(ISomeInterface someInterface)
   {
   }
...
}

そして

public class Service2 : IService2 
{
   public Service2(ISomeInterface someInterface)
   {
   }
...
}

IoCツールとしてAutofacを使用しています。質問。 SomeImpl1がService1に自動的に挿入され、SomeImpl2がService2に自動的に挿入されるようにAutofac登録を構成するにはどうすればよいですか。

ありがとうございました!

26
Andrei M

Autofacは 名前によるサービスの識別 をサポートします。これを使用して、実装を名前で登録できます(Named拡張メソッドを使用)。次に、ResolveNamed拡張メソッドを使用して、IServiceX登録デリゲートで名前でそれらを解決できます。次のコードはこれを示しています。

var cb = new ContainerBuilder();
cb.Register(c => new SomeImpl1()).Named<ISomeInterface>("impl1");
cb.Register(c => new SomeImpl2()).Named<ISomeInterface>("impl2");
cb.Register(c => new Service1(c.ResolveNamed<ISomeInterface>("impl1"))).As<IService1>();
cb.Register(c => new Service2(c.ResolveNamed<ISomeInterface>("impl2"))).As<IService2>();
var container = cb.Build();

var s1 = container.Resolve<IService1>();//Contains impl1
var s2 = container.Resolve<IService2>();//Contains impl2

RegisterTypeを使用する代替(Registerではなく)

RegisterType拡張メソッドをWithParameterおよびResolvedParameterと組み合わせて使用​​すると、同じ結果を得ることができます。これは、名前付きパラメーターを受け取るコンストラクターが、登録デリゲートで指定する必要のない他の名前なしパラメーターも受け取る場合に役立ちます。

var cb = new ContainerBuilder();
cb.RegisterType<SomeImpl1>().Named<ISomeInterface>("impl1");
cb.RegisterType<SomeImpl2>().Named<ISomeInterface>("impl2");
cb.RegisterType<Service1>().As<IService1>().WithParameter(ResolvedParameter.ForNamed<ISomeInterface>("impl1"));
cb.RegisterType<Service2>().As<IService2>().WithParameter(ResolvedParameter.ForNamed<ISomeInterface>("impl2"));
var container = cb.Build();

var s1 = container.Resolve<IService1>();//Contains impl1
var s2 = container.Resolve<IService2>();//Contains impl2
32
bentayloruk

コンストラクターインジェクションからプロパティインジェクションに切り替えて、両方のサービスを同じ基本クラスから派生させる(または同じインターフェイスを実装する)ことができる場合は、次の操作を実行できます。

builder.RegisterType<ServiceBase>().OnActivating(e =>
{
    var type = e.Instance.GetType();

    // get ISomeInterface based on instance type, i.e.:
    ISomeInterface dependency =
        e.Context.ResolveNamed<ISomeInterface>(type.Name);

    e.Instance.SomeInterface = dependency;
});

これを機能させるには、基本タイプ(またはインターフェイス)でプロパティを定義する必要があります。このソリューションは非常に柔軟性があり、以下に示すように、親型に基づいてジェネリック型を挿入するなど、複雑なことも可能になります。

builder.RegisterType<ServiceBase>().OnActivating(e =>
{
    var type = 
       typeof(GenericImpl<>).MakeGenericType(e.Instance.GetType());

    e.Instance.SomeInterface = (ISomeInterface)e.Context.Resolve(type);
});

このアプローチにはいくつかの欠点があります。

  1. プロパティインジェクションが必要です。
  2. そのプロパティを含む基本タイプまたはインターフェイスが必要です。
  3. 型を作成するにはリフレクションが必要です。これは(コンテナが効率的なデリゲートを作成する代わりに)パフォーマンスに影響を与える可能性があります(ただし、型をキャッシュすることで処理を高速化できる場合があります)。

利点として、このデザインはシンプルで、ほとんどすべてのコンテナーで機能します。

4
Steven

これを行う4つのバリエーションが autofacドキュメント で説明されています:

オプション1:インターフェイスを再設計します

同一のサービスを実装するコンポーネントが多数あるが、それらを同一に処理できない状況に遭遇した場合、これは通常、インターフェース設計の問題です。

オブジェクト指向の開発の観点からは、オブジェクトがリスコフの置換原則に準拠している必要がありますが、この種の原則はそれを破ります。

インターフェースを再設計することで、「コンテキストによって依存関係を選択」する必要がなくなります。タイプを使用して区別し、解決中に自動配線の魔法を発生させます。

ソリューションの変更に影響を与えることができる場合は、これが推奨されるオプションです。

オプション2:登録を変更する

この方法で、適切なタイプを消費コンポーネントに手動で関連付けることができます。

var builder = new ContainerBuilder();
builder.Register(ctx => new ShippingProcessor(new PostalServiceSender()));
builder.Register(ctx => new CustomerNotifier(new EmailNotifier()));
var container = builder.Build();

// Lambda registrations resolve based on the specific type, not the
// ISender interface.
builder.Register(ctx => new ShippingProcessor(ctx.Resolve<PostalServiceSender>()));
builder.Register(ctx => new CustomerNotifier(ctx.Resolve<EmailNotifier>()));
var container = builder.Build();

オプション3:キー付きサービスを使用する

builder.RegisterType<PostalServiceSender>()
           .As<ISender>()
           .Keyed<ISender>("order");
    builder.RegisterType<EmailNotifier>()
           .As<ISender>()
           .Keyed<ISender>("notification");

builder.RegisterType<ShippingProcessor>()
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.ResolveKeyed<ISender>("order")));
    builder.RegisterType<CustomerNotifier>();
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.ResolveKeyed<ISender>("notification")));

オプション4:メタデータを使用

builder.RegisterType<PostalServiceSender>()
           .As<ISender>()
           .WithMetadata("SendAllowed", "order");
    builder.RegisterType<EmailNotifier>()
           .As<ISender>()
           .WithMetadata("SendAllowed", "notification");

builder.RegisterType<ShippingProcessor>()
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.Resolve<IEnumerable<Meta<ISender>>>()
                               .First(a => a.Metadata["SendAllowed"].Equals("order"))));
    builder.RegisterType<CustomerNotifier>();
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.Resolve<IEnumerable<Meta<ISender>>>()
                               .First(a => a.Metadata["SendAllowed"].Equals("notification"))));
2
Ivan