KatsuYuzuのブログ

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

ASP.NET MVC 5 で DI する - その3「VBで利用する」 #aspnetjp

ASP.NET MVC 5 で DI する。

今回は VB で利用する方法です。
f:id:KatsuYuzu:20141120235546p:plain:w400

Unity.Mvc を VBで利用する

これまで紹介していた Unity.Mvc ですが、NuGet で容赦なく cs ファイルが追加されるので、VB で利用する際は自分でコンバートする必要があります。
f:id:KatsuYuzu:20141120235556p:plain:w400
単純に頑張るだけなのでコンバート済みソースを Gist に置いておきました。

VB には static class がないので NotInheritable Class に Private コンストラクターで代替しています。コンバート以外には下記を変更しました。

  • VB の MVC プロジェクトの App_Start クラス群に合わせて名前空間を削除
  • UnityConfig の変更される予定のない static フィールドを ReadOnly に変更*1

まとめ

ヴいびー……

*1:他にも UnityConfig 自体 static でいいんじゃないかと気になった箇所がありましたが、極力変更しない方針にしました。

ASP.NET MVC 5 で DI する - その2「生成の一元化」 #aspnetjp

前回、DI をするための基本的なことを説明しました。

今回は DI コンテナによる生成の一元化について説明します。

DI コンテナによる生成の一元化

f:id:KatsuYuzu:20141109235633p:plain:w400
コードは前回からの続きで、今回は Repository で必要になるであろう DB 接続も DI で注入するシナリオで進めます。DB 接続は SqlConnection を利用します。
DB 接続は、これまでに示した簡易なクラスとは違い、コンストラクターで接続文字列を要求しています。このまま Repository に対して SqlConnection を DI した場合、下記の例外が発生します。
f:id:KatsuYuzu:20141112005605p:plain:w400

例外の詳細: System.InvalidOperationException: The type String cannot be constructed. You must configure the container to supply this value.

この問題は、型の登録時に InjectionFactory を用いて生成方法を与えることで解決できます。

var connectionString =
    ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

container.RegisterType<SqlConnection>(
    new PerRequestLifetimeManager(),
    new InjectionFactory(_ => new SqlConnection(connectionString)));

合わせて、確認のために Repository を下記のように変更しました。

public class SampleRepository : ISampleRepository
{
    private readonly SqlConnection connection;

    public SampleRepository(SqlConnection connection)
    {
        this.connection = connection;
        Debug.WriteLine("SampleRepository: Constructor");
        Debug.WriteLine(connection);
    }
}

実行すると、SqlConnection が生成されていることが確認できます。
f:id:KatsuYuzu:20141112004753p:plain:w400

実用例

ここからは DI の説明ではないのですが、せっかくなので少し実用例を紹介します。
@neuecc さんの下記の記事で、SQL のロギングのための MiniProfiler というライブラリが紹介されています。

ここでは記事に倣って ProfiledDbConnection を利用するようにしてみます。

// DbConnection に対して登録しているのがミソで、
// SqlConnection を OracleConnection に変更しても大丈夫です。
// (もちろん SQL 文やストアドは修正が必要でしょう)

// TraceDbProfiler は @neuecc さんの記事を参照
// NLog ではなく Debug.WriteLine を使うように改造して利用

container.RegisterType<DbConnection>(
    new PerRequestLifetimeManager(),
    new InjectionFactory(_ =>
        new ProfiledDbConnection(
            new SqlConnection(connectionString),
            new TraceDbProfiler())));
public class SampleRepository : ISampleRepository
{
    public SampleRepository(DbConnection connection)
    {
        // Dapper ライブラリの拡張メソッドを利用
        var result = connection.ExecuteScalar<string>(
            "SELECT 'Hello, world!'");
        Debug.WriteLine("SampleRepository: " + result);
    }
}

f:id:KatsuYuzu:20141112013524p:plain
これまで通りの手順で、難しいこともなく実現できました。さらに、Repository 自体も ProfiledDbConnection や SQL Server、Oracle などといった DB 接続に依存していません。

まとめ

型の登録時に InjectionFactory で生成方法を与えるだけです。使わない手はないですね!

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 コメントは [表示] > [タスク一覧] で管理されているので簡単に見つけることができます