21
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C#Advent Calendar 2017

Day 4

ASP.NET MVCのフィルターについて掘り下げてみた

Posted at

この記事はC# Advent Calendar 2017の4日目の記事です。

今年はASP.NETのカレンダーが無かったので、C#枠を一つお借りして書いてしまおうと思います。
去年投稿したASP.NET MVCのフィルターを触ってみたの続編みたいな話しです。

本日の疑問

MVCのFilterは色々な所で宣言することが可能です。

  • Global.asaxのApplication_Start(FilterConfigの中)で、GlobalFilters.FiltersにAdd
  • コントローラーの属性に設定
  • アクションメソッドの属性に設定
  • コントローラークラスで実装

ところで、複数宣言された場合はどうなるのか。後勝ち?追加??順番は???
気になったので実際に実装して検証してみました。

検証内容

Filterの実装

まず初めに、Traceログを出力するだけの簡単なFilterを作りました。

Controllerに直接実装するパターン

Controller自体がFillterAttributeを継承している為、直接オーバーライドで各フィルターを実装することが可能です。
このController内に存在するアクション全てに適用されるフィルターになります。

HomeController.cs
public class HomeController : Controller
{
    // ~ Action実装部省略 ~
 
 
    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tController : OnAuthorization");
        base.OnAuthorization(filterContext);
    }
 
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tController : OnActionExecuting");
        base.OnActionExecuting(filterContext);
    }
 
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tController : OnActionExecuted");
        base.OnActionExecuted(filterContext);
    }
 
    protected override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tController : OnResultExecuting");
        base.OnResultExecuting(filterContext);
    }
 
    protected override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tControllerBase : OnResultExecuted");
        base.OnResultExecuted(filterContext);
    }
 
    protected override void OnException(ExceptionContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tController : OnException");
        base.OnException(filterContext);
    }
}

FilterAttributeを継承しての実装パターン

FilterAttributeの継承と実装したいフィルターのインターフェースを実装したカスタムクラスを用意します。
(今回は検証の為にAllowMultipleを True にしていますが、同じフィルターを複数回適用させない場合は不要です。)

AllFilterAttribute.cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class AllFilterAttribute : FilterAttribute, IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter
{
    \\ 何処で宣言されたフィルターか分かるように名前を取る
    public string Target { get; set; }
 
    public AllFilterAttribute(string target)
    {
        Target = target;
    }
 
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tAllFilterAttribute : OnAuthorization(Target:{Target}/Order:{Order})");
    }
 
    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tAllFilterAttribute : OnActionExecuted(Target:{Target}/Order:{Order})");
    }
 
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tAllFilterAttribute : OnActionExecuting(Target:{Target}/Order:{Order})");
    }
 
    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tAllFilterAttribute : OnResultExecuted(Target:{Target}/Order:{Order})");
    }
 
    public void OnResultExecuting(ResultExecutingContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tAllFilterAttribute : OnResultExecuting(Target:{Target}/Order:{Order})");
    }
 
    public void OnException(ExceptionContext filterContext)
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tAllFilterAttribute : OnException(Target:{Target}/Order:{Order})");
    }
}

Filterの適用宣言

続いてフィルターをControllerやActionに適用させていきましょう。

Controllerに直接実装するタイプのフィルターについては、記述と宣言が一緒になっているようなものなので
改めて宣言する必要が無い為、省略しています。

FilterConfigで宣言

Global.asaxから呼び出されるRegisterGlobalFiltersで宣言するパターンです。

FilterConfig.cs
public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new AllFilterAttribute("FilterConfig"));
        filters.Add(new AllFilterAttribute("FilterConfig") { Order = 1 });    // Orderの順序検証用
    }
}

Controller、Actionの属性で宣言

実際にフィルターを使用したいControllerやActionの属性としてフィルタークラスを宣言するパターンです。

HomeController.cs
[AllFilter("Controller")]
[AllFilter("Controller2", Order = 0)]    // Orderの順序検証用
public class HomeController : Controller
{
    [AllFilter("Action")]
    [AllFilter("Action2")]
    public ActionResult Index()
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tIndexActionExecute");
        return new ContentResult()
        {
            Content = "OK"
        };
    }
 
 
    [AllFilter("Action")]
    [AllFilter("Action2")]
    public ActionResult Error()
    {
        Trace.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\tErrorActionExecute");
        // ExceptionFilter確認用
        throw new Exception();
    }
 
    // ~ Filter実装部省略 ~
}

検証結果

通常のパターンと例外発生するパターンの両方を実行してみました。
Traceログを見やすくする為に、改行とコメントは後からいれてます。

通常パターン

// 認証Filter
17:01:25.342    Controller : OnAuthorization
17:01:25.343    AllFilterAttribute : OnAuthorization(Target:FilterConfig/Order:-1)
17:01:25.343    AllFilterAttribute : OnAuthorization(Target:Controller/Order:-1)
17:01:25.344    AllFilterAttribute : OnAuthorization(Target:Action/Order:-1)
17:01:25.347    AllFilterAttribute : OnAuthorization(Target:Action2/Order:-1)
17:01:25.349    AllFilterAttribute : OnAuthorization(Target:Controller2/Order:0)
17:01:25.349    AllFilterAttribute : OnAuthorization(Target:FilterConfig/Order:1)

// Action実行前Filter
17:01:25.354    Controller : OnActionExecuting
17:01:25.354    AllFilterAttribute : OnActionExecuting(Target:FilterConfig/Order:-1)
17:01:25.355    AllFilterAttribute : OnActionExecuting(Target:Controller/Order:-1)
17:01:25.355    AllFilterAttribute : OnActionExecuting(Target:Action/Order:-1)
17:01:25.355    AllFilterAttribute : OnActionExecuting(Target:Action2/Order:-1)
17:01:25.356    AllFilterAttribute : OnActionExecuting(Target:Controller2/Order:0)
17:01:25.356    AllFilterAttribute : OnActionExecuting(Target:FilterConfig/Order:1)
 
// Action処理
17:01:25.366    IndexActionExecute
 
// Action実行後Filter
17:01:25.367    AllFilterAttribute : OnActionExecuted(Target:FilterConfig/Order:1)
17:01:25.367    AllFilterAttribute : OnActionExecuted(Target:Controller2/Order:0)
17:01:25.367    AllFilterAttribute : OnActionExecuted(Target:Action2/Order:-1)
17:01:25.370    AllFilterAttribute : OnActionExecuted(Target:Action/Order:-1)
17:01:25.370    AllFilterAttribute : OnActionExecuted(Target:Controller/Order:-1)
17:01:25.370    AllFilterAttribute : OnActionExecuted(Target:FilterConfig/Order:-1)
17:01:25.370    Controller : OnActionExecuted
 
// Result返却前Filter
17:01:25.373    Controller : OnResultExecuting
17:01:25.373    AllFilterAttribute : OnResultExecuting(Target:FilterConfig/Order:-1)
17:01:25.373    AllFilterAttribute : OnResultExecuting(Target:Controller/Order:-1)
17:01:25.374    AllFilterAttribute : OnResultExecuting(Target:Action/Order:-1)
17:01:25.374    AllFilterAttribute : OnResultExecuting(Target:Action2/Order:-1)
17:01:25.378    AllFilterAttribute : OnResultExecuting(Target:Controller2/Order:0)
17:01:25.378    AllFilterAttribute : OnResultExecuting(Target:FilterConfig/Order:1)
 
// ここで結果が返るはず・・・
 
// Result返却後Filter
17:01:25.379    AllFilterAttribute : OnResultExecuted(Target:FilterConfig/Order:1)
17:01:25.379    AllFilterAttribute : OnResultExecuted(Target:Controller2/Order:0)
17:01:25.379    AllFilterAttribute : OnResultExecuted(Target:Action2/Order:-1)
17:01:25.381    AllFilterAttribute : OnResultExecuted(Target:Action/Order:-1)
17:01:25.381    AllFilterAttribute : OnResultExecuted(Target:Controller/Order:-1)
17:01:25.381    AllFilterAttribute : OnResultExecuted(Target:FilterConfig/Order:-1)
17:01:25.381    Controller : OnResultExecuted

例外パターン

// 認証Filter
17:05:06.719    Controller : OnAuthorization
17:05:06.720    AllFilterAttribute : OnAuthorization(Target:FilterConfig/Order:-1)
17:05:06.723    AllFilterAttribute : OnAuthorization(Target:Controller/Order:-1)
17:05:06.723    AllFilterAttribute : OnAuthorization(Target:Action/Order:-1)
17:05:06.724    AllFilterAttribute : OnAuthorization(Target:Action2/Order:-1)
17:05:06.724    AllFilterAttribute : OnAuthorization(Target:Controller2/Order:0)
17:05:06.725    AllFilterAttribute : OnAuthorization(Target:FilterConfig/Order:1)
 
// Action実行前Filter
17:05:06.728    Controller : OnActionExecuting
17:05:06.731    AllFilterAttribute : OnActionExecuting(Target:FilterConfig/Order:-1)
17:05:06.731    AllFilterAttribute : OnActionExecuting(Target:Controller/Order:-1)
17:05:06.732    AllFilterAttribute : OnActionExecuting(Target:Action/Order:-1)
17:05:06.734    AllFilterAttribute : OnActionExecuting(Target:Action2/Order:-1)
17:05:06.750    AllFilterAttribute : OnActionExecuting(Target:Controller2/Order:0)
17:05:06.751    AllFilterAttribute : OnActionExecuting(Target:FilterConfig/Order:1)
 
// Action処理
17:05:06.752    ErrorActionExecute
 
// Action実行後Filter
17:05:06.781    AllFilterAttribute : OnActionExecuted(Target:FilterConfig/Order:1)
17:05:06.794    AllFilterAttribute : OnActionExecuted(Target:Controller2/Order:0)
17:05:06.805    AllFilterAttribute : OnActionExecuted(Target:Action2/Order:-1)
17:05:06.814    AllFilterAttribute : OnActionExecuted(Target:Action/Order:-1)
17:05:06.823    AllFilterAttribute : OnActionExecuted(Target:Controller/Order:-1)
17:05:06.832    AllFilterAttribute : OnActionExecuted(Target:FilterConfig/Order:-1)
17:05:06.841    Controller : OnActionExecuted
 
// 例外Filter
17:05:06.850    AllFilterAttribute : OnException(Target:FilterConfig/Order:1)
17:05:06.851    AllFilterAttribute : OnException(Target:Controller2/Order:0)
17:05:06.851    AllFilterAttribute : OnException(Target:Action2/Order:-1)
17:05:06.854    AllFilterAttribute : OnException(Target:Action/Order:-1)
17:05:06.854    AllFilterAttribute : OnException(Target:Controller/Order:-1)
17:05:06.854    AllFilterAttribute : OnException(Target:FilterConfig/Order:-1)
17:05:06.856    MyHandleErrorAttribute : OnException
17:05:06.858    Controller : OnException

結果まとめ

実行順について

以下の項目を上から順に判定して決められている。

  1. Orderプロパティ
  2. 宣言箇所
    3. Controller
    4. FilterConfig
    5. ControllerでのAttribute
    6. ActionでのAttribute
  3. 宣言順

ただし、昇順 or 降順についてはメソッド毎に異なる。

  • 昇順で実行されるメソッド
    • IAuthorizationFilter.OnAuthorization
    • IActionFilter.OnActionExecuting
    • IResultFilter.OnResultExecuting
  • 降順で実行されるメソッド
    • IActionFilter.OnActionExecuted
    • IResultFilter.OnResultExecuted
    • IExceptionFilter.OnException

昇順と降順については以下のような特徴があるように見える。
メイン処理の「前」に実行されるフィルター処理 → 昇順
メイン処理の「後」に実行されるフィルター処理 → 降順

例外発生時の処理

Actionメソッド内で例外が発生した場合でも、例外発生後にIActionFilter.OnActionExecutedメソッドは実行される為、
ここでActionが成功している前提の処理は書かないほうが無難。

IResultFilterの処理は実行されない為、「結果が返る!」と分かったときの処理はここに集約する。

IExceptionFilter.OnExceptionは実装されている全てが呼び出される。
(今回の実験結果には含まれてないが)Resultの値は後勝ちになるので、注意が必要。

21
30
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?