KatsuYuzuのブログ

.NET の開発をメインとした日記です。

ASP.NET MVC 5 で DI する #aspnetjp

ASP.NET MVC は DI(Dependency Injection: 依存性の注入)がとても簡単です。
DI については、何かに依存するものを外からもらうことで依存せずに済むくらいに思ってください。テストがしやすくなることや、クラスが絡み合わずに済むことなどがメリットとして挙げられます。さらに、DI コンテナ(依存したものの入れ物)でオブジェクトの管理もできるので、DB 接続の管理(生成の一元化、自動破棄など)などでもメリットがあります。

プロジェクトの作成とライブラリの参照

まずは、MVC プロジェクトを作成します*1
f:id:KatsuYuzu:20141109235609p:plain:w400
f:id:KatsuYuzu:20141109235622p:plain:w400
DI の仕組みには patterns & practices お手製の Unity を使います。NuGet で Unity で検索して Unity.Mvc をインストールしてください。
f:id:KatsuYuzu:20141109235633p:plain:w400
パッケージマネージャーコンソールで行う場合は下記の通りです。

PM> Install-Package Unity.Mvc

ライブラリの参照を終えたら実装開始です。

DI の実装

始めに、今回の DI の対象であるインターフェイスと実装クラスを作成します。

public interface ISampleRepository
{
    // 枠だけ使いたいので空っぽ
}

public class SampleRepository : ISampleRepository, IDisposable
{
    public SampleRepository()
    {
        Debug.WriteLine("SampleRepository: Constructor");
    }

    public void Dispose()
    {
        Debug.WriteLine("SampleRepository: Dispose");
    }
}

次に、このインターフェイスに対して実装クラスが注入されるように登録します。実は、NuGet によって設定用のファイルが 2 つ追加されていて、何をすべきか TODO コメントで記述されています*2
f:id:KatsuYuzu:20141109235645p:plain:w400
指示の通りにコメントアウトされていたコードを書き換えます。

// TODO: Register your types here
container.RegisterType<ISampleRepository, SampleRepository>();

最後に、コントローラーのコンストラクターで、先ほど登録したインターフェイスを受け取るようにすると DI が行われます。

private readonly ISampleRepository repository;

public HomeController(ISampleRepository repository)
{
    this.repository = repository;
}

DI は伝搬する

ある程度のシステム規模の場合、コントローラーと Repository の間に、処理を行う Service 層を設けると思います。その場合は、コントローラーのコンストラクターで Service 、Service のコンストラクターで Repository を、それぞれ受け取るようにします。コントローラーの生成に伴う DI は伝搬しますので、下記は思った通りに動作します。

public class HomeController : Controller
{
    private readonly SampleService service;

    public HomeController(SampleService service)
    {
        this.service = service;
    }

    public ActionResult Index()
    {
        return View();
    }
}


public class SampleService
{
    private readonly ISampleRepository repository;

    public SampleService(ISampleRepository repository)
    {
        this.repository = repository;
    }
}


public interface ISampleRepository
{
    // 枠だけ使いたいので空っぽ
}

public class SampleRepository : ISampleRepository, IDisposable
{
    public SampleRepository()
    {
        Debug.WriteLine("SampleRepository: Constructor");
    }

    public void Dispose()
    {
        Debug.WriteLine("SampleRepository: Dispose");
    }
}

このとき、Service に関しては型が明確なので登録は不要です。

オブジェクトのライフサイクル

DI コンテナでは、オブジェクトのライフサイクルも管理されています。型の登録時に PerRequestLifetimeManager を使うことで、管理されているオブジェクトはリクエストの終了時に破棄されるようになります。DbContext や DbConnection を扱う際にとても有効です。
まずは、もう一つの TODO コメントに従って PerRequestLifetimeManager を有効化します。
f:id:KatsuYuzu:20141109235645p:plain:w400

// TODO: Uncomment if you want to use PerRequestLifetimeManager
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility
    .RegisterModule(typeof(UnityPerRequestHttpModule));

あとは、型の登録時に PerRequestLifetimeManager を指定するだけです。

container.RegisterType<ISampleRepository, SampleRepository>(
    new PerRequestLifetimeManager());

実行すると、Dispose が呼ばれていることが確認できます。
f:id:KatsuYuzu:20141109235653p:plain:w400
先ほどの Service のように型が明確でもライフサイクルを指定したい場合は登録が必要です。

まとめ

NuGet で Unity.Mvc をインストールして、TODO コメントに従うだけです。使わない手はないですね!

*1:ハローワールドのノリで触るのであればテンプレートの選択で認証を無しにしておくと良いでしょう。認証がある場合、サンプルで結構な量のコードが展開されるのでシステム規模が肥大化してしまいます

*2:TODO コメントは [表示] > [タスク一覧] で管理されているので簡単に見つけることができます