ASP.NET MVC 3 で DIする方法について調べたことをまとめておきます。
ここでは主にControllerに対するDIについてのまとめを残しておきます。Googleで色々検索していると、ASP.NET MVC 3 では、DependencyInjection のための拡張ポイントが3つあるように見えます。
ひとつは IDependencyResolver というインターフェース。これを実装すればよい、という人もいれば、他にも IControllerActivator も一緒に実装するサンプルを実装してみせる人もいれば、さらには IControllerFactory というインターフェースがどうのこうの言う人までいて、情報が錯綜しているように見えます。
結論から言えば、Unity やMEF, Seasar.NET などなんらかの DIContainer を使用して DI する場合に限ると、IDependencyResolver だけを実装すれば良いようです。
詳しくは Do not implement IControllerActivator in ASP.NET MVC 3 に書いてある通りなのですが、ICotrollerActivator の実装は、ほとんど意味がないのです。もちろん要件にもよりますが、 IControllerActivator が必要になるような要件は存在しないでしょう。
IDependencyResolver の実装が登録されていると、ASP.NET MVC3 はリクエスト時のパイプラインに従って拡張ポイントの実装が登録されているかどうかをひとつひとつ聞いてきます。
- IControllerFactory の実装は登録されていますか?されているならそれをください。
- IControllerActivator の実装は登録されていますか?されているならそれをください。
- ~~Controllerは登録されていますか?されているならそれをください。
- ・・・続く(Filter、ViewEngine、cshtml など)
IDepndencyResolver の実装が IControllerActivator の実装を ASP.NET MVC 3 へちゃんと渡してあげれば、ASP.NET MVC 3 は Controller の生成時にちゃんと IControllerActivator の実装を使ってくれます。ちなみに各インターフェースの実装を ASP.NET MVC 3 へ渡さないと、(具体的には null を返却すると) ASP.NET MVC 3 はデフォルトの実装を使用します。Unity による IDependencyResolver の実装例は次のようになります。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Microsoft.Practices.Unity; namespace MvcWithUnity.DI { public class UnityDependencyResolver : IDependencyResolver { IUnityContainer container; public UnityDependencyResolver(IUnityContainer container) { this.container = container; } public object GetService(Type serviceType) { try { return this.container.Resolve(serviceType); } catch { return null; } } public IEnumerable<object> GetServices(Type serviceType) { try { return this.container.ResolveAll(serviceType); } catch { return new List<object>(); } } } }
メソッド GetService の 仮引数である serviceType が IControllerActivator である場合、つまり先ほどのパイプラインの 2 の時、この時に Unity Container が Resolve した結果、IControllerActivator の実装を返却できるように Unity Container に登録しておけばよいわけです。そして ASP.NET MVC 3 にこの IDependencyResolver の登録をするには Global.asax へ次のように実装します。
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); var container = new UnityContainer().LoadConfiguration(); DependencyResolver.SetResolver(new MvcWithUnity.DI.UnityDependencyResolver(container)); }
Unity へのオブジェクトの登録は、config ファイルによる登録にしています。 LoadConfiguration メソッドを使っている部分がそれにあたります。
(余談ですが、 LoadConfiguration メソッドを使うには Microsoft.Practices.Unity.Configuration 名前空間への using が必要です)
では config ファイルへの記述例を・・・と、その前に IControllerActivator の実装をします。 DependencyResolver へ Contoller の生成を委譲する実装になります。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcWithUnity.DI { public class UnityControllerActivator : IControllerActivator { public IController Create(System.Web.Routing.RequestContext requestContext, Type controllerType) { // 現在使用中の DependencyResolver を使用して、 contoller を生成して返却しています。
return DependencyResolver.Current.GetService(controllerType) as IController; } } }
段々ややこしくなってきましたが、要するに次のような流れです。
IControllerActivator の存在確認
ASP.NET MVC 3 → IDependencyResolver
この存在確認は、アプリケーションの初回起動時にしか行われません。 IDependencyResolver の戻り値は今回の場合 UnityControllerActivator です。
Controller 生成の委譲 ( with IControllerActivator )
ASP.NET MVC 3 → IControllerActivator → IDependencyResolver → Unity Container
ここが IControllerActivator の意味を無くしている点です。実はASP.NET MVC 3 はIControllerActivator の登録がない場合は、IDependencyResolver に対して Controller の生成を委譲するのです。
Contoller 生成の委譲 ( without IControllerActivator )
ASP.NET MVC 3 → IDependencyResolver → Unity Container
つまり、IControllerActivator の実装があってもなくても結果は同じなのです。だったら、余計な実装は少ないほうがよい、ということで IControllerActivator の実装はしないほうがよいのでは、という結論になります。
最後に Unity Container にオブジェクトの登録を Config ファイルに実装する例は次のとおりです。
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<namespace name="MvcWithUnity.DI" />
<namespace name="System.Web.Mvc" />
<assembly name="MvcWithUnity" />
<assembly name="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<container>
<!--<register type="HomeController" />
コントローラを登録する必要はありません。登録していない型をResolveすると、Unityはその型を生成してくれます。 もしコントローラが仮引数ありのコンストラクタを持つ場合、その仮引数のオブジェクトがUnity に登録されていれば Unity はそのオブジェクトを 仮引数に Injection しつつコントローラを生成してくれるからです。 ––>
<register type="IControllerActivator" mapTo="UnityControllerActivator" />
</container>
</unity >
ではなぜ IControllerActivator という拡張ポイントが提供されているのでしょうか。本来、IDependencyResolver では拡張ポイントに対する自前の実装だけを 登録するだけ、という使い方が正しいのかもしれません。今回の場合は IControllerActivator の実装だけを登録する、ということです。たとえば Controller を生成する DIContainer と、Filter や View を生成する DIContainer は別のものを使いたい、という要件にも応えることがことができます。 つまり、 Controller を生成する クラス は、このクラスなんだよ、と明確にする必要がある場合は IControllerActivator を実装する意味はあるのです。
ではまず、 IControllerActivator だけを登録する IDependencyResolver の実装例です。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcWithUnity.DI { public class UnityDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { try { if (serviceType.Equals(typeof(IControllerActivator))) { return new UnityControllerActivator(); } else return null; } catch { return null; } } public IEnumerable<object> GetServices(Type serviceType) { return new List<object>(); } } }
仮引数 serviceType が IControllerActivator のときだけ、IControllerActivator の実装である UnityControllerActivator を返却しています。次はUnityControllerActivator の実装例を。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.Configuration; namespace MvcWithUnity.DI { public class UnityControllerActivator : IControllerActivator { IUnityContainer container; public UnityControllerActivator() { this.container = new UnityContainer().LoadConfiguration(); } public IController Create(System.Web.Routing.RequestContext requestContext, Type controllerType) { return this.container.Resolve(controllerType) as IController; } } }
コンストラクタで Unity Container を生成しているところが変更点です。 Global.asax への UnityDependencyResolver の登録は次のようになります。
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); DependencyResolver.SetResolver(new MvcWithUnity.DI.UnityDependencyResolver()); }
Unity Container を生成していた部分がなくなりました。純粋に UnityDependencyResolver を登録しているだけです。 config ファイルによる Unity Container へのオブジェクト登録は次のようになります。
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
</container>
</unity>
空っぽになりました。
というわけで、Controller に対する DI だけにフォーカスして実装すると、IControllerActivator の実装が必要ではありますが、とてもシンプルでわかりやすくなります。
では、なぜ IControllerActivator を使わないのでしょうか。恐らく、 IDependencyResolver の実装内で Controller を生成 & DI することが要件として難しい、という場面が実際のプロジェクトではほとんど出てこないのしょう。実際、 何も不都合がないと思いませんか?
というわけで、結論として IDpendencyResolver の実装だけでOK!ということです。