ASP.NET CoreアプリのDependency Injection処理を別のDIコンテナに委譲する

# 最初に補足

コントローラー以外でもDI連携する方法について[完全版](http://qiita.com/yamaokunousausa/items/bc81d8498ecf62da0208)も記述したので、そちらも参照してください

# やること

ASP.NET CoreではDependency Injectionが標準機能として組み込まれていますが、それでは機能不足な事も多いと思います。

そこで、他のDIコンテナとASP.NET Coreを連携させる方法について記述します。

通常、各種DIコンテナについては、その拡張としてASP.NET Coreとのインテグレーション機能が提供されると思いますが、今回は自作のDependency Resolverを使用して、連携方法の仕組み自体について記述します。

この方法と同じやり方をすることで、インテグレーション機能が提供されていないコンテナについても、ASP.NET Coreとの連携が可能となります。

# 環境

* Visual Strudio 2015
* .NET Core Tooling Preview 2 for Visual Studio 2015
* [Smart.Resolver 1.0.4](https://github.com/usausa/Smart-Net-Resolver) (自作のGuice型Dependency Resolver)

# 前提

ASP.NET Core Web Applicationを作成し、NuGetでUsa.Smart.Resolverを追加しておきます。

# 手順

## IControllerActivator実装

IControllerActivatorの派生クラスとして、以下のような実装を用意します。

なお、IResolverはSmart.ResolverのDependency Resolverインターフェースになります。

```csharp:SmartResolverControllerActivator.cs
using System;

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;

using Smart.Resolver;

public class SmartResolverControllerActivator : IControllerActivator
{
    private readonly IResolver resolver;

    public SmartResolverControllerActivator(IResolver resolver)
    {
        this.resolver = resolver;
    }

    public object Create(ControllerContext context)
    {
        return resolver.Get(context.ActionDescriptor.ControllerTypeInfo.AsType());
    }

    public void Release(ControllerContext context, object controller)
    {
        (controller as IDisposable)?.Dispose();
    }
}
```

## Startup修正

StartupクラスでStandardResolverをメンバに定義し、ConfigureServices()でSmartResolverControllerActivatorの設定を行います。

```csharp:Startup.cs
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;

using Smart.Resolver;

public class Startup
{
    private readonly StandardResolver resolver = new StandardResolver();
...
    public void ConfigureServices(IServiceCollection services)
    {
        // 標準のASP.NET設定はここ

        services.AddSingleton<IControllerActivator>(new SmartResolverControllerActivator(resolver));
    }
...
}
```

IControllerActivatorが設定されると、コントローラーのインスタンスはその実装経由で行われるようになります。

この例では、実際のインスタンスの生成をSmart.ResolverのDependency Resolver実装であるStandardResolverに委譲することで、ASP.NET CoreとSmart.Resolverの連携を実現しています。

# サンプル

実際に動くサンプルを以下に用意しました。

* [DependencyInjectionExample](https://github.com/usausa/Example-Net-AspNetCore/tree/master/DependencyInjectionExample)

このサンプルは、用途毎に複数のDB接続があるようなアプリケーションを想定したものです。

接続を生成する複数のIConnectionFactoryのインスタンスについて、ASP.NET Coreの標準ではサポートされない条件付きバインディングを行っています。

なお、SQLiteとDapperを使用し、実際にデータアクセスまで行っています。

## インスタンス生成委譲対象

サンプルで、StandardResolverにインスタンスの生成/管理を委譲する主なクラスについて以下に記述します。

| クラス              | スコープ      | 概要                                                 |
| :------------------ | :------------ | :--------------------------------------------------- |
| CharacterController | Prototype     | CharacterServiceを使用するAPIコントローラー          |
| ItemController      | Prototype     | ItemServiceを使用するAPIコントローラー               |
| CharacterService    | Singleton     | 名称"Character"のCallbackConnectionFactoryを使用する |
| ItemService         | Singleton     | 名称"Master"のCallbackConnectionFactoryを使用する    |
| IConnectionFactory  | Singleton * 2 | "Character"、"Master"の2つのインスタンスが存在する   |

## Resolver初期化コード

Resolverの初期化コードを以下に抜粋します。

```csharp
var connectionStringMaster = Configuration.GetConnectionString("Master");
resolver
    .Bind<IConnectionFactory>()
    .ToConstant(new CallbackConnectionFactory(() => new SqliteConnection(connectionStringMaster)))
    .Named("Master");
var connectionStringCharacter = Configuration.GetConnectionString("Character");
resolver
    .Bind<IConnectionFactory>()
    .ToConstant(new CallbackConnectionFactory(() => new SqliteConnection(connectionStringCharacter)))
    .Named("Character");

resolver
    .Bind<MasterService>()
    .ToSelf()
    .InSingletonScope()
    .WithConstructorArgument("connectionFactory", kernel => kernel.Get<IConnectionFactory>("Master"));
resolver
    .Bind<CharacterService>()
    .ToSelf()
    .InSingletonScope()
    .WithConstructorArgument("connectionFactory", kernel => kernel.Get<IConnectionFactory>("Character"));
```

CallbackConnectionFactoryについては、接続情報の異なる2つのインスタンスを、Named()メソッドにより異なる名称で登録しています。

2つのServiceクラスについては、Singletonスコープとして登録し、インスタンス生成時のコンストラクタ引数connectionFactoryについて、名称指定でResolverから取得して設定するようにしています。

なお、Named()及びWithConstructorArgument()によって条件付きバインディングを行っていますが、名称による条件付きバインディングはNamedAttributeを使う事でも可能です。

また、Smart.Resolverでは明示的に情報を登録しないクラスについてはPrototypeスコープとして扱われるため、コントローラーについてはResolverへの情報登録は不要となっています。

# うさコメ

IControllerActivatorの他に、IViewComponentActivatorとかもありますでよ(・ω・)

# 参考文献

* [作って理解するDependency Resolver(なぜかDIコンテナと言わないというこだわり)](http://d.hatena.ne.jp/machi_pon/20160917/1474102787) - Smart.Resolverに関する落書きメモ