フィルタって?
ASP.NET MVCでは、「System.Web.Mvc.Controllerクラスを継承した自作コントローラークラス」がロジックの中核を担います。以下の例はデフォルト生成されたコントロールクラスです。
public class HomeController : Controller { public ActionResult Index(string comment) { ViewData["Message"] = "Hello World的な何か"; return View(); } (中略) }
この例では、単純に"<ドメイン名>/Home/"にアクセスした場合にViewDataにデータをつめて返すだけの処理を実装しています。このアクション実行前後に汎用的な処理を行いたい場合、ASP.NET MVCの機能であるフィルターを実行する事で実現する事が可能です。Java出身者な人たちには「サーブレットフィルタみたいな奴だよ」とか言えばわかって貰えるかも知れません。
どうやって動作させる?
フィルタはコントローラに対して、属性として付与する事が可能です。具体的には以下な感じで付与できます。
[SampleClassFilter] public class DummyController : Controller { [SampleMethodFilter] public ActionResult Index(string comment) { if (string.IsNullOrEmpty(comment) == false) { ViewData["Message"] = "送信したコメント : " + comment; } return View(); } public ActionResult List() { return View(); } (中略) }
この例で、SampleClassFilterはクラス全体に対して付与し、SampleMethodFilterはメソッドに対してのみ付与しています。従って、以下の様な感じ
- Indexアクション実行時
- SampleClassFilter、SampleMethodFilter両方実行
- Listアクション実行時
- SampleClassFilterのみ実行
Filterの作り方
属性として定義するので、以下のクラスを継承する必要があります。
- 必須クラス
- FilterAttribute
- 任意クラス
- IAuthorizationFilter(認証前に処理を追加)
- IActionFilter(アクション実行前後に処理を追加)
- IResultFilter(ActionResult#ExecuteResult実行前後に処理を追加)
- IExceptionFilter(例外発生時の処理を追加)
サンプルコードは以下になります。フィルターを「クラスのみ、メソッドのみ」とそれぞれ付与を制御する為には、AttributeUsage属性の引数で制御します。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class CustomRapidFilterAttribute : FilterAttribute, IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter { public void OnAuthorization(AuthorizationContext filterContext) { Trace.WriteLine("Filter1:OnAuthorization()" + filterContext.HttpContext.User.Identity.Name); } public void OnActionExecuting(ActionExecutingContext filterContext) { Trace.WriteLine("Filter1:OnActionExecuting()"); } public void OnActionExecuted(ActionExecutedContext filterContext) { Trace.WriteLine("Filter1:OnActionExecuted()"); } public void OnResultExecuting(ResultExecutingContext filterContext) { Trace.WriteLine("Filter1:OnResultExecuting()"); } public void OnResultExecuted(ResultExecutedContext filterContext) { Trace.WriteLine("Filter1:OnResultExecuted()"); } public void OnException(ExceptionContext filterContext) { Trace.WriteLine("Filter1:OnException()"); } }
順序制御
Filter一個での実装・動作に関しては Usa*Usaさんのはてだ d:id:machi_pon:20090405:1238857545 を見ていただくとして、複数のFilterが有った場合についてです。実は順番制御の為にSystem.Web.Mvc.FilterAttributeにOrderという拡張ポイントが用意されております。以下はFilterAttributeの実装を抜粋。
public abstract class FilterAttribute : Attribute { private int _order = -1; public int Order { get { return _order; } set { if (value < -1) { throw new ArgumentOutOfRangeException("value", MvcResources.FilterAttribute_OrderOutOfRange); } _order = value; } } }
Orderの値の大きさによって順序制御をする事になっており、デフォルト値は-1、-1より小さい値を設定すると例外を吐く設定になっています。
動作確認
後で示すサンプルコードをもとに、実際にフィルターを実行した際のログを提示します。
HomeController:OnAuthorization() Filter2:OnAuthorization() Filter1:OnAuthorization() HomeController:OnActionExecuting() Filter2:OnActionExecuting() Filter1:OnActionExecuting() //実はここでアクションを実行している Filter1:OnActionExecuted() Filter2:OnActionExecuted() HomeController:OnActionExecuted() HomeController:OnResultExecuting() Filter2:OnResultExecuting() Filter1:OnResultExecuting() //ここでActionResult#ExecuteResult実行 Filter1:OnResultExecuted() Filter2:OnResultExecuted() HomeController:OnResultExecuted()
ごらんの通り、値が大きいほど先に実行されるという優先順位になっています。XXXXExecutedメソッドに関しては実行順序が逆になっていますので、その点には気をつける必要があります。
サンプルのコード
- コントローラのソースコード
[CustomRapidFilter] [CustomLateFilter] public class HomeController : Controller { //このアクション実行時に”CustomRapidFilter”、”CustomLateFilter”が実行される public ActionResult Index(string comment) { ViewData["Message"] = "Hello World的な何か"; return View(); } (中略) }
- Filterのソースコード×2
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class CustomRapidFilterAttribute : FilterAttribute, IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter { public CustomRapidFilterAttribute() { // この値で順序制御。大きいほうが優先度が高い Order = 30; } public void OnAuthorization(AuthorizationContext filterContext) { Trace.WriteLine("Filter1:OnAuthorization()" + filterContext.HttpContext.User.Identity.Name); } public void OnActionExecuting(ActionExecutingContext filterContext) { Trace.WriteLine("Filter1:OnActionExecuting()"); } public void OnActionExecuted(ActionExecutedContext filterContext) { Trace.WriteLine("Filter1:OnActionExecuted()"); } public void OnResultExecuting(ResultExecutingContext filterContext) { Trace.WriteLine("Filter1:OnResultExecuting()"); } public void OnResultExecuted(ResultExecutedContext filterContext) { Trace.WriteLine("Filter1:OnResultExecuted()"); } public void OnException(ExceptionContext filterContext) { Trace.WriteLine("Filter1:OnException()"); } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class CustomLateFilterAttribute : FilterAttribute, IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter { public CustomLateFilterAttribute() { // この値で順序制御。大きいほうが優先度が高い Order = 20; } public void OnAuthorization(AuthorizationContext filterContext) { Trace.WriteLine("Filter2:OnAuthorization()"); } public void OnActionExecuting(ActionExecutingContext filterContext) { Trace.WriteLine("Filter2:OnActionExecuting()"); } public void OnActionExecuted(ActionExecutedContext filterContext) { Trace.WriteLine("Filter2:OnActionExecuted()"); } public void OnResultExecuting(ResultExecutingContext filterContext) { Trace.WriteLine("Filter2:OnResultExecuting()"); } public void OnResultExecuted(ResultExecutedContext filterContext) { Trace.WriteLine("Filter2:OnResultExecuted()"); } public void OnException(ExceptionContext filterContext) { Trace.WriteLine("Filter2:OnException()"); } }