ASP.NET MVC - FilterAttribute.OrderとFilterScopeによるフィルタの実行順を確認する
FilterAttribute.OrderとFilterScopeによるフィルタの実行順を確認します。
同じフィルタが複数ある場合に
- FilterAttributeのOrderプロパティの昇順(小さい値から大きい値の順序)
- FilterScopeの昇順
でフィルタがソートされて実行される、というあたりを確認します。
FilterAttribute.Order
Orderプロパティは想像しやすいかなと思います。
なので略。
FilterScope
FilterScopeはMVC内部で使われているもので、使う側が指定するものではないようです。
ざっくり言うとフィルタの指定した場所によって決まるものという解釈でいいのかなと思います。
- FilterScope.First・・・コントローラ自体のフィルタ
- FilterScope.Global・・・グローバルフィルタに登録したフィルタ
- FilterScope.Controller・・・コントローラのクラスに指定したFilterAttribute
- FilterScope.Action・・・アクションメソッドに指定したFilterAttribute
- FilterScope.Last・・・定義されているだけで使われていないような・・・?
といった感じです。昇順もこの順番です。
FilterScope.Firstを少し補足します。
コントローラは各フィルタのインターフェイスを実装しています。メソッドをオーバーライドすることによってフィルタの処理を書くことができます。このときコントローラ自体のスコープはFilterScope.Firstとなります。またOrderはInt32.MinValueとして扱われているようです。つまりコントローラ自体のフィルタ(オーバーライドしたメソッド)は必ず最初に実行されることになります。
サンプルで確認
ここからはアクションフィルタに絞ってサンプルコードで確認してみます。
まずはアクションフィルタを実装したフィルタ属性を用意します。この属性はトレース出力だけしています。
// アクションフィルタだけを実装したフィルタ属性 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public class TestFilterAttribute : FilterAttribute, IActionFilter { public string Label { get; set; } // 確認用に出力する文字列 public void OnActionExecuting(ActionExecutingContext filterContext) { // Orderとラベルをトレース Trace.WriteLine($"{nameof(TestFilterAttribute)}({Order}, \"{Label}\").{nameof(OnActionExecuting)}"); } public void OnActionExecuted(ActionExecutedContext filterContext) { Trace.WriteLine($"{nameof(TestFilterAttribute)}({Order}, \"{Label}\").{nameof(OnActionExecuted)}"); } }
次に用意した属性をグローバルフィルタに設定します。Orderを省略した場合は-1になります。
// グローバルフィルタとして属性追加(ラベル:"Global") // Orderを指定しない場合は-1 GlobalFilters.Filters.Add(new TestFilterAttribute { Label = "Global" }); GlobalFilters.Filters.Add(new TestFilterAttribute { Order = 1, Label = "Global" }); GlobalFilters.Filters.Add(new TestFilterAttribute { Order = 2, Label = "Global" });
さらに用意した属性をコントローラのクラスとメソッドにも指定します。またコントローラ自体のアクションフィルタもオーバーライドしてトレースするようにしておきます。
// クラスに属性(ラベル:"Controller") [TestFilter(Label = "Controller")] [TestFilter(Order = 1, Label = "Controller")] [TestFilter(Order = 2, Label = "Controller")] public class DefaultController : Controller { // コントローラ自体のアクションフィルタ protected override void OnActionExecuting(ActionExecutingContext filterContext) { Trace.WriteLine($"{nameof(DefaultController)}.{nameof(OnActionExecuting)}"); } protected override void OnActionExecuted(ActionExecutedContext filterContext) { Trace.WriteLine($"{nameof(DefaultController)}.{nameof(OnActionExecuted)}"); } // メソッドに属性(ラベル:"Action") [TestFilter(Label = "Action")] [TestFilter(Order = 1, Label = "Action")] [TestFilter(Order = 2, Label = "Action")] public ActionResult Index() { Trace.WriteLine($"{nameof(DefaultController)}.{nameof(Index)}"); return new EmptyResult(); } }
そしてデバッグ実行すると出力結果は次のように。わかりやすいように改行しつつコメントも足しています。
// コントローラ自体のメソッドが真っ先に呼ばれる DefaultController.OnActionExecuting // Order = -1(Orderを指定しない)のアクションフィルタ // Global、Controller、Actionの順で実行される TestFilterAttribute(-1, "Global").OnActionExecuting TestFilterAttribute(-1, "Controller").OnActionExecuting TestFilterAttribute(-1, "Action").OnActionExecuting // Order = 1のアクションフィルタ TestFilterAttribute(1, "Global").OnActionExecuting TestFilterAttribute(1, "Controller").OnActionExecuting TestFilterAttribute(1, "Action").OnActionExecuting // Order = 2のアクションフィルタ TestFilterAttribute(2, "Global").OnActionExecuting TestFilterAttribute(2, "Controller").OnActionExecuting TestFilterAttribute(2, "Action").OnActionExecuting // ここでアクションメソッドの呼び出し DefaultController.Index // OnActionExecutedは逆順 TestFilterAttribute(2, "Action").OnActionExecuted TestFilterAttribute(2, "Controller").OnActionExecuted TestFilterAttribute(2, "Global").OnActionExecuted TestFilterAttribute(1, "Action").OnActionExecuted TestFilterAttribute(1, "Controller").OnActionExecuted TestFilterAttribute(1, "Global").OnActionExecuted TestFilterAttribute(-1, "Action").OnActionExecuted TestFilterAttribute(-1, "Controller").OnActionExecuted TestFilterAttribute(-1, "Global").OnActionExecuted DefaultController.OnActionExecuted
Orderの昇順、Orderが同じ場合はグローバルフィルタ、クラスのフィルタ属性、メソッドのフィルタ属性の順(FilterScopeの昇順)で実行されることを確認できました。さらにOnActionExecutingは上記の実行順で実行され、OnActionExecutedはその逆順で実行されることも確認できました。
ちなみにOrderもFilterScopeも同じ場合の実行順は未定義とのことです。
種類、順序、スコープが同じフィルターの実行順序は未定義です。
おしまい。