ASP.NET MVCについてもチマチマと、とりあえず基本のあたり(3)

VCのCの拡張ってIControllerFactoryの拡張ポイントの話で、コントローラ自体の拡張ポイントの話が抜けていたので、フィルタについて書いてみます(・∀・)

フィルタ

ASP.NET MVCには以下の4つのインタフェースが存在し、これらの実装クラスを用意することで、コントローラの処理ステージの前後に、独自の処理を挟み込む事が可能になっています(・∀・)

  • IAuthorizationFilter 認証処理というか、前処理用。
  • IActionFilter コントローラのアクションメソッドの実行前後処理用。
  • IResultFilter ActionResultの実行前後処理用。
  • IExceptionFilter 例外発生時の処理用。

また、これらのフィルタが呼び出される順番は、通常は(フィルタの途中でActionResultを設定したりしなければ)下記のよう順になります。

  1. IAuthorizationFilter.OnAuthorization()
  2. IActionFilter.OnActionExecuting()
  3. コントローラのアクションメソッド実行
  4. IActionFilter.OnActionExecuted()
  5. IResultFilter.OnResultExecuting()
  6. ActionResult.ExecuteResult()
  7. IResultFilter.OnResultExecuted()
  8. IExceptionFilter.OnException() (例外発生時のみ)

なお、標準で用意されている実装としては、OutputCache(出力キャッシュ)、HandleError(エラーハンドリング)、Authorize(IPrincipalによる認証)等が存在します。

独自フィルタの作成

独自のフィルタを作成する場合、上記いずれかのインタフェースを実装したFilterAttribute派生クラスを作成するか、FilterAttributeを派生してIActionFilter、IResultFilterを実装したActionFilterAttributeクラスが標準で用意されているので、ActionFilterAttribute派生クラスを作成することになります。
FilterAttribute派生クラスを作成してコントローラクラスに属性を付与すると、フレームワーク内でそのフィルタインタフェースの処理を呼び出してくれるようになっています。


試しに、4つのインタフェース全てを実装し、処理の各段でデバッグ出力を行う下記のようなFilterAttribute派生クラスを作成してみます(・∀・)

[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class CustomFilterAttribute : FilterAttribute, IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        Trace.WriteLine( "Filter:OnAuthorization()" );
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Trace.WriteLine( "Filter:OnActionExecuting()" );
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        Trace.WriteLine( "Filter:OnActionExecuted()" );
    }

    public void OnResultExecuting(ResultExecutingContext filterContext)
    {
        Trace.WriteLine( "Filter:OnResultExecuting()" );
    }

    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Trace.WriteLine( "Filter:OnResultExecuted()" );
    }

    public void OnException(ExceptionContext filterContext)
    {
        Trace.WriteLine( "Filter:OnException()" );
    }
}

次にCustomFilterAttributeを適用するコントローラクラスとして以下のようなものを用意します。
Controllerクラスも4つのインタフェースを実装しており、コントローラ固有のフィルタ処理であればメソッドをoverrideすることによりそこに処理を書くことも可能です。

[CustomFilter]
public class FilterController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        Trace.WriteLine( "Controller:OnAuthorization()" );
    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Trace.WriteLine( "Controller:OnActionExecuting()" );
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        Trace.WriteLine( "Controller:OnActionExecuted()" );
    }

    protected override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        Trace.WriteLine( "Controller:OnResultExecuting()" );
    }

    protected override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Trace.WriteLine( "Controller:OnResultExecuted()" );
    }

    protected override void OnException(ExceptionContext filterContext)
    {
        Trace.WriteLine( "Controller:OnException()" );
    }
}

っで、上記コントローラを実行すると、各フィルタの実行順序が下記のように確認できます。

Controller:OnAuthorization()
Filter:OnAuthorization()
Controller:OnActionExecuting()
Filter:OnActionExecuting()
Filter:OnActionExecuted()
Controller:OnActionExecuted()
Controller:OnResultExecuting()
Filter:OnResultExecuting()
Filter:OnResultExecuted()
Controller:OnResultExecuted()

なお、複数のフィルタを適用する場合の適用順序についてですが、FilterAttributeはOrderプロパティを持っており、その値を指定することにより制御が可能になっています(`・ω・´)


…ってなカンジ(・∀・)
IControllerFactoryとIViewEngine以外の話はまとめて書こうと思っていたけれど、フィルタの話が長くなったので独立させまスタ(´ω`)