閱讀34 返回首頁    go 阿裏雲 go 技術社區[雲棲]


ASP.NET MVC中的ActionFilter是如何執行的?

在ASP.NET MVC中的四大篩選器(Filter),ActionFilter直接應用在某個Action方法上,它在目標Action方法執行前後對調用進行攔截以執行一些額外的操作。這是一種典型的AOP式的設計,如果我們需要在執行某個Action方法的前後執行一些操作,可以通過定義ActionFilter來實現。本篇文章主要講述多一個應用到相同Action方法上的ActionFilter的執行機製。[本文已經同步到《How ASP.NET MVC Works?》中]

目錄
一、ActionFilter
二、ActionFilter的執行機製
三、ActionFilter對ActionResult的設置
四、ActionFilter中的異常處理

ActionFilter允許我們在目標Action方法執行前後對調用進行攔截以執行一些額外的操作,所有的ActionFilter實現了具有如下定義的接口IActionFilter

   1: public interface IActionFilter
   2: {    
   3:     void OnActionExecuting(ActionExecutingContext filterContext);
   4:     void OnActionExecuted(ActionExecutedContext filterContext);
   5: }
   6:  
   7: public class ActionExecutingContext : ControllerContext
   8: {    
   9:     public ActionExecutingContext();
  10:     public ActionExecutingContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> actionParameters);
  11:     
  12:     public virtual ActionDescriptor            ActionDescriptor { get; set; }
  13:     public virtual IDictionary<string, object> ActionParameters { get; set; }
  14:     public ActionResult                        Result { get; set; }
  15: }
  16:  
  17: public class ActionExecutedContext : ControllerContext
  18: {    
  19:     public ActionExecutedContext();   
  20:     public ActionExecutedContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, bool canceled, Exception exception);
  21:     
  22:     public virtual ActionDescriptor     ActionDescriptor { get; set; }
  23:     public virtual bool                 Canceled { get; set; }
  24:     public virtual Exception            Exception { get; set; }
  25:     public bool                         ExceptionHandled { get; set; }
  26:     public ActionResult                 Result { get; set; }
  27: }

如上麵的代碼片斷所示,IActionFilter接口中定義了兩個方法OnActionExecuting和OnActionExecuted,這兩個方法分別在目標Action方法執行前後被調用,它們的參數類型分別為ActionExecutingContextActionExecutedContext。這兩個上下文了類型均是ControllerContext的子類。

我們可以從ActionExecutingContext對象中獲取到用於描述當前Action的ActionDescriptor,以及參數列表。ActionFilter可以在OnActionExecuting方法中對ActionExecutingContext對象的Result屬性進行賦值來直接響應當前的請求。一旦ActionExecutingContext的Result屬性被成功賦值,將會終止後續ActionFilter和最終目標方法的執行。

ActionExecutedContext具有額外的三個屬性,Exception表示執行Action方法過程中拋出的異常,而ExceptionHandled是一個表示是否對異常已經做出處理的標記。Canceled屬性表示沒有完成整個ActionFilter鏈和目標Action方法的執行而中途被終止。

image當ActionInvoker在執行目標Action方法之前,會根據Order和Scope屬性對用於封裝ActionFilter的Filter對象進行排序。然後根據當前ControllerContext和ActionDescriptro創建一個ActionExecutingContext對象,並將其作為參數依次調用所有ActionFilter的OnActionExecuting方法。

在這之後真正的目標Action方法被執行,ActionInvoker隨後執行後續的篩選操作。具體來說,它根據當前ControllerContext、ActionDescriptro以及Action方法執行過程中拋出的異常創建一個ActionExecutedContext對象。該ActionExecutedContext的Cancel屬性為False,如果Action方法返回一個ActionResult對象,該對象將會作為該ActionExecutedContext的Result屬性。

接下來按照相反的次序依次調用ActionFilter對象的OnActionExecuted方法,執行過程中的ActionFilter可以修改ActionExecutedContext的Result屬性。當整個ActionFilter鏈執行結束之後,ActionExecutedContext的Result屬性返回的ActionResult將會作為對當前請求的響應。右圖基本上反映了連同目標Action在內的整個ActionFilter鏈的執行過程。

上麵我們已經提到過,在ActionFilter鏈進行OnActionExecuting方法調用的過程中,。實際上此時ActionInvoker此時會創建一個ActionExecutedContext對象,設置的ActionResult直接作為其Result屬性,而Cancel屬性被設置為True。我們現在考慮的問題是:之前的ActionFilter的OnActionExecuted是否還被執行呢?

為了弄清楚這個問題,我們來創建一個測試程序。在通過Visual Studio的ASP.NET MVC項目模板創建的空Web應用中我們定義了如下三個ActionFilter(FooAttribute、BarAttribute和BazAttribute),它們都繼承自我們自定義的FilterBaseAttribute。在FilterBaseAttribute中實現的OnActionExecuting和OnActionExecuted方法中,我們將ActionFilter自身的類型和執行方法名寫入當前HttpResponse並最終呈現在瀏覽器中。BarAttribute重寫了OnActionExecuting方法,在調用基類同名方法之後為ActionExecutingContext的Result設置了一個EmptyResult對象。

   1: public abstract class FilterBaseAttribute : FilterAttribute, IActionFilter
   2: {
   3:     public virtual void OnActionExecuted(ActionExecutedContext filterContext)
   4:     {
   5:         filterContext.HttpContext.Response.Write(string.Format("{0}.OnActionExecuted()<br/>", this.GetType().Name));
   6:     }
   7:  
   8:     public virtual void OnActionExecuting(ActionExecutingContext filterContext)
   9:     {
  10:         filterContext.HttpContext.Response.Write(string.Format("{0}.OnActionExecuting()<br/>", this.GetType().Name));
  11:     }
  12: }
  13:  
  14: public class FooAttribute : FilterBaseAttribute
  15: {}
  16: public class BarAttribute : FilterBaseAttribute
  17: {
  18:     public override void OnActionExecuting(ActionExecutingContext filterContext)
  19:     {
  20:         base.OnActionExecuting(filterContext);
  21:         filterContext.Result = new EmptyResult();
  22:     }
  23: }
  24: public class BazAttribute : FilterBaseAttribute
  25: {}

然後我們定義了如下一個HomeController,上麵定義的三個ActionFilter特性被應用到了Action方法Index上。我們對三個ActionFilter特性的Order屬性作了相應地設置使它們可以按照我們希望的順序(FooAttribute =>BarAttribute =>BazAttribute)執行。

   1: public class HomeController : Controller
   2: {
   3:     [Foo(Order = 1)]
   4:     [Bar(Order = 2)]
   5:     [Baz(Order = 3)]
   6:     public void Index()
   7:     {
   8:         Response.Write("Index...</br>");
   9:     }
  10: }

image運行該程序後會在瀏覽器中呈現出如左圖所示的輸出結果,從中可以看出對於應用到Action方法Index上的三個ActionFilter,當BarAttribute的OnActionExecuting方法執行並對ActionExecutingContext的Result屬性進行了相應設置後,在它之前的ActionFilter的OnActionExecuted方法依然還是會執行。image

這個簡單的實例演示揭示了應用到同一個Action方法上的ActionFilter鏈的執行機製:如果某個某個ActionFilter在執行OnActionExecuting方法過程中對ActionExecutingContext的Result屬性進行了設置,後續的ActionFilter和目標Action方法將不會再執行。此時ActionExecutedContext對象被創建,通過ActionExecutingContext的Result屬性表示的ActionResulut對象將會賦值給ActionExecutedContext的Result屬性。然後以前一個ActionFilter作為起點將創建的ActionExecutedContext對象作為輸入參數調用它們的OnActionExecuted方法。右圖基本上揭示了整個ActionFilter鏈執行的流程。順便指出一點:某個ActionFilter在OnActionExecuted方法中對ActionExecutedContext的Result的設置對整個ActionFilter鏈的執行沒有影響。

image通過上麵的介紹我們知道了某個ActionFilter在執行OnActionExecuting/OnActionExecuted方法過程中設置ActionExecutingContext/ActionExecutedContext的Result屬性進行設置後會對整個ActionFilter鏈的執行造成怎樣的影響,接下來我們來討論一下如果某個ActionFilter在執行OnActionExecuting/OnActionExecuted方法拋出異常,整個ActionFilter鏈又會如何執行。

如果第一個ActionFilter在執行OnActionExecuting或者OnActionExecuted方法的過程中出現異常,那麼這個異常會被直接拋出。對於出現異常的並不是第一個ActionFilter,那麼異常會被捕捉並據此創建一個ActionExecutedContext對象(其Canceled屬性為False)作為參數調用前一個ActionFilter的OnActionExecuted方法。在前一個ActionFilter的OnActionExecuted方法執行之後ActionExecutedContext的ExceptionHandled屬性為True,會按照正常的方式調用之前ActionFilter的OnActionExecuted方法。

反之,如果ExceptionHandled屬性為False,則會直接將異常拋出來,而這個被拋出的異常又會被之前的ActionFilter捕捉到,而這個ActionFilter又會根據捕捉的異常創建一個ActionExecutedContext對象並調用自身的OnActionExecuted方法。如果異常是在非鏈頭的ActionFilter的OnActionExecuted方法中拋出的,處理流程與此類似。

我們不妨舉例說明Action鏈在執行過程中對異常的處理。假設具有如左圖所示的4個ActionFilter被應用到目標Action方法上,現在Filter1、Filter2和Filter3的OnActionExecuting方法異常被正常調用,但是Filter4在執行OnActionExecuting方法的時候拋出一個異常。該異常會被Filter3捕捉,它會根據這個異常創建一個ActionExecutedContext對象,並作為參數調用自己的OnActionExecuted方法(步驟1)。

如果Filter3在執行OnActionExecuted方法後ActionExecutedContext的ExceptionHandled屬性為False,它會直接將異常拋出來。再次拋出的異常又會被Filter2所捕捉,它按照Filter3的方式根據異常創建ActionExecutedContext對象並作為參數調用自己的OnActionExecuted方法(步驟2)。如果Filter2在執行OnActionExecuted方法的時候將ActionExecutedContext對象的ExceptionHandled屬性設置為True,那麼在這之後會正常地調用Filter1的OnActionExecuted方法,最終不會有異常拋出(步驟3)。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 17:04:19

  上一篇:go  通過Knockout.js + ASP.NET Web API構建一個簡單的CRUD應用
  下一篇:go  ASP.NET MVC集成EntLib實現“自動化”異常處理[實例篇]