ASP.NET MVC 5 で DI する - その3「VBで利用する」 #aspnetjp
ASP.NET MVC 5 で DI する。
- ASP.NET MVC 5 で DI する #aspnetjp - KatsuYuzuのブログ
- ASP.NET MVC 5 で DI する - その2「生成の一元化」 #aspnetjp - KatsuYuzuのブログ
今回は VB で利用する方法です。
Unity.Mvc を VBで利用する
これまで紹介していた Unity.Mvc ですが、NuGet で容赦なく cs ファイルが追加されるので、VB で利用する際は自分でコンバートする必要があります。
単純に頑張るだけなのでコンバート済みソースを 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 コンテナによる生成の一元化
コードは前回からの続きで、今回は Repository で必要になるであろう DB 接続も DI で注入するシナリオで進めます。DB 接続は SqlConnection を利用します。
DB 接続は、これまでに示した簡易なクラスとは違い、コンストラクターで接続文字列を要求しています。このまま Repository に対して SqlConnection を DI した場合、下記の例外が発生します。
例外の詳細: 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 が生成されていることが確認できます。
実用例
ここからは 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); } }
これまで通りの手順で、難しいこともなく実現できました。さらに、Repository 自体も ProfiledDbConnection や SQL Server、Oracle などといった DB 接続に依存していません。
まとめ
型の登録時に InjectionFactory で生成方法を与えるだけです。使わない手はないですね!
目次
ASP.NET MVC 5 で DI する #aspnetjp
ASP.NET MVC は DI(Dependency Injection: 依存性の注入)がとても簡単です。
DI については、何かに依存するものを外からもらうことで依存せずに済むくらいに思ってください。テストがしやすくなることや、クラスが絡み合わずに済むことなどがメリットとして挙げられます。さらに、DI コンテナ(依存したものの入れ物)でオブジェクトの管理もできるので、DB 接続の管理(生成の一元化、自動破棄など)などでもメリットがあります。
プロジェクトの作成とライブラリの参照
まずは、MVC プロジェクトを作成します*1。
DI の仕組みには patterns & practices お手製の Unity を使います。NuGet で Unity で検索して Unity.Mvc をインストールしてください。
パッケージマネージャーコンソールで行う場合は下記の通りです。
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。
指示の通りにコメントアウトされていたコードを書き換えます。
// 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 を有効化します。
// TODO: Uncomment if you want to use PerRequestLifetimeManager Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility .RegisterModule(typeof(UnityPerRequestHttpModule));
あとは、型の登録時に PerRequestLifetimeManager を指定するだけです。
container.RegisterType<ISampleRepository, SampleRepository>(
new PerRequestLifetimeManager());
実行すると、Dispose が呼ばれていることが確認できます。
先ほどの Service のように型が明確でもライフサイクルを指定したい場合は登録が必要です。
まとめ
NuGet で Unity.Mvc をインストールして、TODO コメントに従うだけです。使わない手はないですね!