系列目錄
在研究ASP.NET MVC2中IActionFilter和IResultFilter的執行邏輯的時候看到下面四個方法(你可以在ControllerActionInvoker.cs中找到它們)
- InvokeActionMethodWithFilters
- InvokeActionMethodFilter
- InvokeActionResultWithFilters
- InvokeActionResultFilter
事實上前兩個和後兩個的實現和邏輯幾乎差不多,只不過一組處理IActionFilter,一組處理IResultFilter,這裡我只討論一下前兩組。
在閱讀本文前,建議您搞清楚.NET的Func<>委託、集合的Aggregate方法、匿名方法等相關知識。
先來看看InvokeActionResultWithFilters,它的含義是執行包含IActionFilter過濾器的Action:
protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) { ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters); Func<ActionExecutedContext> continuation = () => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) }; // need to reverse the filter list because the continuations are built up backward Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation, (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next)); return thunk();}
我不打算回溯到它的調用點討論,因此這裡需要解釋一下這個函數被調用的背景和各個傳入參數的意義。在函數調用之前,所有的準備工作已經做好,這些準備工作包括:定位Action;針對這個Action相關的所有IActionFilter和IResultFilter已經反射出來,並儲存下來了;通過了IAuthorizationFilter驗證;Action參數已經準備妥當。
- ControllerContext controllerContext:不用多說,整個Controller和Action的執行過程中封裝的上下文參數,本文不涉及;
- IList<IActionFilter> filters:該Action相關的IActionFilter介面集合,本文將討論到。需要注意的是這個集合裡面將包含這個Action所在的Controller自身實現的IActionFilter,所以這個集合裡面至少有一個IActionFilter;
- ActionDescriptor actionDescriptor:封裝了該action的描述,通過其Execute來真正執行Action,本文不涉及;
- IDictionary<string, object> parameters:提供Action所需的參數,本文不涉及。
逐行解讀InvokeActionResultWithFilters
ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
我們知道一個Aciton有不止一個IActionFilter,他們依照某種順序執行,這裡的preContext對象相當於依次接受這些IActionFilter的OnActionExecuting的結果。
Func<ActionExecutedContext> continuation = () => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) };
Func<ActionExecutedContext> continuation是個委託,指向返回ActionExecutedContext的函數,後半部分是個匿名方法
() => new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) { Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters) };
表示沒有傳入參數,返回一個ActionExecutedContext並且,其中的Result為真正的action執行後的結果。當continuation()執行時將執行這個匿名方法。
至此,continuation是一個委派物件,功能就是真正執行action。
Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation, (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
又是一個Func<ActionExecutedContext>的委託 thunk,其定義為一個彙總(Aggregate)方法的結果(請記住,這裡的Aggregate返回的是個委派物件),在彙總前先要反轉一下IList<IActionFilter> filters,Aggregate第二個參數的含義是:將filters裡面的每一個filter和前一次的彙總操作的結果作為參數,返回一個Func<ActionExecutedContext>的委派物件,它是:
() => InvokeActionMethodFilter(filter, preContext, next)
這個委派物件本身是執行InvokeActionMethodFilter,這個方法需要當前的filter和上一個彙總操作的結果(一個Func<ActionExecutedContext>的委派物件)作為參數。而這整個彙總操作的初始Func<ActionExecutedContext>委派物件是上面執行個體化的continuation。
我實在找不出其他的辭藻能夠描述這段代碼的含義了,請參考
途中左側橘色表示filters集合中的IActionFilter,紅色箭頭表示彙總操作,每次彙總操作的結果是個Func<ActionExecutedContext>委派物件,最終彙總結果賦給thunk。這個彙總對象都將invoke一個叫InvokeAtionMethodFilter的函數,並且其中一個參數指向了上一個彙總對象的結果(就是代碼中的next)。
注意,這裡彙總的結果僅僅返回一個委託,其中的方法還沒有執行,真正開始執行是這樣的:
return thunk();
這將導致最後返回的委派物件所指向的函數被執行。
現在是時候看看InvokeAtionMethodFilter函數到底是怎麼執行的了,我只先貼出上半部分:
internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation){filter.OnActionExecuting(preContext);if (preContext.Result != null) { return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */) { Result = preContext.Result };}bool wasError = false;ActionExecutedContext postContext = null;try { postContext = continuation();}…
首先調用filter的OnActionExecuting,然後判斷preContext.Result是否被修改,假設現在為null,那麼就表示這個filter通過了!接著在try中執行continuation。還記得continuation是什麼嗎?是指向上一個Func<ActionExecutedContext>委派物件的引用,在這裡調用意味著遞迴調用上一個InvokeActionMethodFilter!如果這個遞迴鏈在執行過程中能通過每個filter的層層把關的話,調用到最後將是真正的執行action!
現在知道Aggregate方法實際上構造了一個遞迴調用鏈;現在明白為什麼上一個彙總對象的結果要命名為next和continuation了:這是相對於執行時而言當然是“下一個”或者是“繼續”的意思啦;我不得不佩服微軟的工程師巧妙的用幾行代碼構造了一個遞迴鏈。下面我們來看看下半部分catch塊的代碼:
catch (ThreadAbortException) { // This type of exception occurs as a result of Response.Redirect(), but we special-case so that // the filters don't see this as an error. postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */); filter.OnActionExecuted(postContext); throw;}catch (Exception ex) { wasError = true; postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex); filter.OnActionExecuted(postContext); if (!postContext.ExceptionHandled) { throw; }}if (!wasError) { filter.OnActionExecuted(postContext);}return postContext;}
這裡的catch包含兩部分,第一個catch中的注釋已經說明了問題。再仔細思考第二個catch塊你就會明白在深入理解ASP.NET MVC(8)中提到的:為什麼當前action異常會被上一個層次的捕獲的原因了,因為這裡是個遞迴調用被包含在try\catch中。
最後談談在Aggregate前執行的反轉。如果你試著調試這段代碼,或者你研究過之前的代碼細節,我告訴你在剛進InvokeActionMethodWithFilters的時候,參數filters的第一個對象是controller自己的filter,你一定不會感到詫異。試想,如果沒有這裡的反轉,直接彙總的結果將是controller自己的filter最後執行,而MVC的設計就是讓controller自己的filter優先於其他的Attribute。
為了理解這段代碼著實花了我不少時間,當我想通的瞬間真是豁然開朗,拍案叫絕!寥寥幾筆,便形成了一個優雅乾淨的遞迴調用,不仔細看都不能發現遞迴的痕迹!
勞動果實,轉載請註明出處:http://www.cnblogs.com/P_Chou/archive/2010/12/18/asp-net-mvc-src-recursive-using-aggregate-delegate.html