nosu blog

Web界隈からエンタープライズITに転身したときのメモとか

UnityやAutofacでDIする際に、同じ型の複数のインスタンスを注入する方法

.NETで開発をしていると、クラスを別の依存クラスの実装と分離するために、Dependency Injection(DI)を使うことは多い。 その際に、一種類の依存するインタフェースにつき、一つだけインスタンスを渡せば良いのであれば話は単純なのだが、一つのインタフェースでも、コンストラクタの異なる複数のインスタンスを渡したい、というようなケースがある。

例えば、以下のようなケースである。

  • IHttpClient (実装は HttpClient) という、何らかのHTTPリクエストを送るためのクライアントがある
  • HttpClient はコンストラクタの引数としてHTTP接続の際のベースURLを受け取るとする。
  • このクライアントを利用する SampleController では、2つの異なるベースURLに接続する必要があるため、それぞれコンストラクタの異なる2つのHttpClientのインスタンスを利用したい

以下のやり方で、このような場合にも対応できる。

Unity を使っている場合

DIのライブラリにUnityを使っている場合、Unityの持つ名前付きでRegisterTypeする機能を使う。

var BaseUriA = "a.example.com"; // 1つ目のベースUri
var BaseUriB = "b.example.com"; // 2つ目のベースUri

var container = new UnityContainer();
container.RegisterType<IHttpClient, HttpClient>(
    "HttpClientA", // 一つ目の引数として任意の名前を渡す
    new InjectionConstructor(BaseUriA)
);
container.RegisterType<IHttpClient, HttpClient>(
    "HttpClientB",
    new InjectionConstructor(BaseUriB)
);

// 上でつけた名前を使って、2つのHttpClientをSampleControllerのコンストラクタに渡す
container.RegisterType<SampleController>(new InjectionFactory(c =>
    new SampleController(
        c.Resolve<IHttpClient>("HttpClientA"),
        c.Resolve<IHttpClient>("HttpClientB")
    )
));
public class SampleController : ApiController {
    private IHttpClient HttpClientA;
    private IHttpClient HttpClientB;

    public SampleController(IHttpClient HttpClientA, IHttpClient HttpClientB) {
        this.HttpClientA = HttpClientA;
        this.HttpClientB = HttpClientB;
    }

}

Autofac を使っている場合

Unityはもはやメンテされていないため、今後新しいプロジェクトでは利用しないことが望ましい。代替としてよく名前の上がるAutofacの場合、Named and Keyed Services という機能で目的の挙動が実現できる。 依存を受け取る側で、IIndex<K, V>という型で複数インスタンスを受け取り、名前を指定することで目的のインスタンスを取り出す。

var BaseUriA = "a.example.com"; # 1つ目のベースUri
var BaseUriB = "b.example.com"; # 2つ目のベースUri

var builder = new ContainerBuilder();
builder.RegisterType<HttpClient>()
    .WithParameter(new TypedParameter(typeof(string), BaseUriA))
    .Named<IHttpClient>("HttpClientA");
builder.RegisterType<HttpClient>()
    .WithParameter(new TypedParameter(typeof(string), BaseUriB))
    .Named<IHttpClient>("HttpClientB");
public class SampleController : ApiController {
    private IIndex<HttpClient, IHttpClient> httpClients;

    public SampleController(IIndex<HttpClient, IHttpClient> httpClients) {
        this.httpClients = httpClients;
    }

    public IHttpClient GetClientA() {
        return httpClients["HttpClientA"];
    }
}

参考