根據controller的名字正確的執行個體化了一個controller對象。回到MVCHandler的BeginProcessRequest方法,可以看到,當得到controller對象之後,首先判斷它是不是IAsyncController,如果是則會建立委託用來非同步執行。通常情況下,我們都是繼承自Controller類,這不是一個IAsyncController,於是會直接執行Controller的Execute方法。Execute方法是在Controller的基類ControllerBase中定義的,這個方法除去一些安全檢查,初始化了ControllerContext(包含了ControllerBase和Request的資訊),核心是調用了ExecuteCore方法,這在ControllerBase是個抽象方法,在Controller類中有實現:
複製代碼 代碼如下:
protected override void ExecuteCore() {
PossiblyLoadTempData();
try {
string actionName = RouteData.GetRequiredString("action");
if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
HandleUnknownAction(actionName);
}
}
finally {
PossiblySaveTempData();
}}
這個方法比較簡單,首先是載入臨時資料,這僅在是child action的時候會出現,暫不討論。接下來就是擷取action的名字,然後InvokeAction, 這裡的ActionInvoker是一個ControllerActionInvoker類型的對象,我們來看它的InvokeAction方法,
複製代碼 代碼如下:
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) {
if (controllerContext == null) {
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
if (actionDescriptor != null) {
FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
try {
AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
if (authContext.Result != null) {
// the auth filter signaled that we should let it short-circuit the request
InvokeActionResult(controllerContext, authContext.Result);
}
else {
if (controllerContext.Controller.ValidateRequest) {
ValidateRequest(controllerContext);
}
IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
}
}
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.
throw;
}
catch (Exception ex) {
// something blew up, so execute the exception filters
ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
if (!exceptionContext.ExceptionHandled) {
throw;
}
InvokeActionResult(controllerContext, exceptionContext.Result);
}
return true;
}
// notify controller that no method matched
return false;}
這是一個非常核心的方法,有很多工作在這裡面完成。ASP.NET MVC中有幾個以Descriptor結尾的類型,首先獲得ControllerDescriptor,這個比較簡單,實際返回的是ReflectedControllerDescriptor對象。第二步實際上是調用了ReflectedControllerDescriptor的FindAction方法,獲得ActionDescriptor,ActionDescriptor最重要的屬性是一個MethodInfo,這就是當前action name對應的Action的方法。FindAction方法內部實際上是調用了ActionMethodSelector的FindActionMethod來獲得MethodInfo,可以想象,這個方法將會反射controller的所有方法的名字,然後和action name匹配,實際上,ASP.NET還支援一些額外的功能,主要是: 1.通過ActionNameAttribute屬性重新命名action的名字;2.支援ActionMethodSelectorAttribute對action方法進行篩選,比如[HttpPost]之類的。下面簡單看下ActionMethodSelector的實現,大致分為4步,首先是在建構函式中調用了如下方法反射controller中的所有action方法:
複製代碼 代碼如下:
private void PopulateLookupTables() {
MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(method => method.Name, StringComparer.OrdinalIgnoreCase);
}FindActionMethod方法如下:
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) {
List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
switch (finalMethods.Count) {
case 0:
return null;
case 1:
return finalMethods[0];
default:
throw CreateAmbiguousMatchException(finalMethods, actionName);
} }
這個方法是很清晰的,找到重新命名之後符合的,本身名字元合的,然後所有的方法判斷是否滿足ActionMethodSelectorAttribute的條件,最後或者返回匹配的MethodInfo,或者拋出異常,或者返回null。三個步驟的實現並不困難,不再分析下去。
第三步是得到Filter。 FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);實際調用的是:
FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor);這裡的代碼風格和之前的不太一樣,特別喜歡用各種委託,讀代碼有點困難,估計不是同一個人寫的。下面的分析都直接給出實際執行的代碼。首先看下FilterProvider的建構函式:
複製代碼 代碼如下:
static FilterProviders() {
Providers = new FilterProviderCollection();
Providers.Add(GlobalFilters.Filters);
Providers.Add(new FilterAttributeFilterProvider());
Providers.Add(new ControllerInstanceFilterProvider());
}
回憶下ASP.NET給Action加上filter的方法一共有如下幾種:
1. 在Application_Start註冊全域filter
2. 通過屬性給Action方法或者Controller加上filter
3. Controller類本身也實現了IActionFilter等幾個介面。通過重寫Controller類幾個相關方法加上filter。
這三種方式就對應了三個FilterProvider,這三個Provider的實現都不是很困難,不分析了。到此為止,準備工作都好了,接下來就會執行Filter和Action,ASP.NET的Filter一共有4類:
Filter Type |
Interface |
Description |
Authorization |
IAuthorizationFilter |
Runs first |
Action |
IActionFilter |
Runs before and after the action method |
Result |
IResultFilter |
Runs before and after the result is executed |
Exception |
IExceptionFilter |
Runs if another filter or action method throws an exception |
下面看其原始碼的實現,首先就是InvokeAuthorizationFilters:
複製代碼 代碼如下:
protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor) {
AuthorizationContext context = new AuthorizationContext(controllerContext, actionDescriptor);
foreach (IAuthorizationFilter filter in filters) {
filter.OnAuthorization(context);
if (context.Result != null) {
break;
}
}
return context;}
注意到在實現IAuthorizationFilter介面的時候,要表示驗證失敗,需要在OnAuthorization方法中將參數context的Result設定為ActionResult,表示驗證失敗後需要顯示的頁面。接下來如果驗證失敗就會執行context的Result,如果成功就要執行GetParameterValues獲得Action的參數,在這個方法內部會進行Model Binding,這也是ASP.NET的一個重要特性,另文介紹。再接下來會分別執行InvokeActionMethodWithFilters和InvokeActionResultWithFilters,這兩個方法的結構是類似的,只是一個是執行Action方法和IActionFilter,一個是執行ActionResult和IResultFilter。以InvokeActionMethodWithFilters為例分析下:
複製代碼 代碼如下:
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();
}
這段代碼有點函數式的風格,不熟悉這種風格的人看起來有點難以理解。 用函數式程式設計語言的話來說,這裡的Aggregate其實就是foldr,
foldr::(a->b->b)->b->[a]->b
foldr 接受一個函數作為第一個參數,這個函數的參數有兩個,類型為a,b,傳回型別為b,第二個參數是類型b,作為起始值,第三個參數是一個類型為a的數組,foldr的功能是依次將數組中的a 和上次調用第一個參數函數(f )的傳回值作為f的兩個參數進行調用,第一次調用f的時候用起始值。對於C#來說,用物件導向的方式表示,是作為IEnummerable的一個擴充方法實現的,由於C# 不能直接將函數作為函數的參數傳入,所以傳入的是委託。說起來比較拗口,看一個例子:
複製代碼 代碼如下:
static void AggTest()
{
int[] data = { 1, 2, 3, 4 };
var res = data.Aggregate("String", (str, val) => str + val.ToString());
Console.WriteLine(res);
}
最後輸出的結果是String1234. 回到InvokeActionMethodWithFilters的實現上來,這裡對應的類型a是IActionFilter,類型b是Func<ActionExecutedContext>,初始值是continuation。假設我們有3個filter,[f1,f2,f3],我們來看下thunk最終是什麼,
第一次: next=continue, filter=f1, 傳回值 ()=>InvokeActionMethodFilter(f1, preContext, continue)
第二次:next=()=>InvokeActionMethodFilter(f1, preContext, continue), filter=f2
傳回值:()=>InvokeActionMethodFilter(f2, preContext,()=> InvokeActionMethodFilter(f1, preContext, continue)),
最終: thunk= ()=>InvokeActionMethodFilter(f3,preContext,()=>InvokeActionMethodFilter(f2, preContext, ()=>InvokeActionMethodFilter(f1, preContext, continue)));
直到 return thunk()之前,所有真正的代碼都沒有執行,關鍵是構建好了thunk這個委託,把thunk展開成上面的樣子,應該比較清楚真正的調用順序什麼樣的了。這裡花了比較多的筆墨介紹了如何通過Aggregate方法構造調用鏈,這裡有一篇文章專門介紹了這個,也可以參考下。想象下,如果filter的功能就是先遍曆調用f的Executing方法,然後調用Action方法,最後再依次調用f的Executed方法,那麼完全可以用迭代來實現,大可不必如此抽象複雜,關鍵是ASP.NET MVC對於filter中異常的處理還有一些特殊之處,看下InvokeActionMethodFilter的實現:
複製代碼 代碼如下:
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();
}
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;
}
代碼有點長,首先就是觸發了filter的OnActionExecuting方法,這是方法的核心。接下來的重點是 postContext = continuation(); 最後是OnActionExecuted方法,結合上面的展開式,我們可以知道真正的調用順序將是:
複製代碼 代碼如下:
f3.Executing->f2.Executing->f1.Exectuing->InvokeActionMethod->f1.Executed->f2->Executed->f3.Executed.
那麼,原始碼中的注釋 // need to reverse the filter list because the continuations are built up backward 的意思也很明了了。需要將filter倒序排一下之後才是正確的執行順序。
還有一類filter是當異常發生的時候觸發的。在InvokeAction方法中可以看到觸發它的代碼放在一個catch塊中。IExceptionFilter的觸發流程比較簡單,不多做解釋了。唯一需要注意的是ExceptionHandled屬性設定為true的時候就不會拋出異常了,這個屬性在各種context下面都有,他們是的效果是一樣的。比如在OnActionExecuted方法中也可以將他設定為true,同樣不會拋出異常。這些都比較簡單,不再分析其原始碼,這篇文章比較詳細的介紹了filter流程中出現異常之後的執行順序。
最後說下Action Method的執行,前面我們已經得到了methodInfo,和通過data binding獲得了參數,調用Action Method應該是萬事俱備了。asp.net mvc這邊的處理還是比較複雜的,ReflectedActionDescriptor會去調用ActionMethodDispatcher的Execute方法,這個方法如下:
複製代碼 代碼如下:
public object Execute(ControllerBase controller, object[] parameters) {
return _executor(controller, parameters);
}
此處的_executor是
delegate object ActionExecutor(ControllerBase controller, object[] parameters);_exectuor被賦值是通過一個方法,利用Expression拼出方法體、參數,代碼在(ActionMethodDispatcher.cs):
static ActionExecutor GetExecutor(MethodInfo methodInfo)此處就不貼出了,比較複雜。這裡讓我比較費解的是,既然MethodInfo和parameters都有了,直接用反射就可以了,為什麼還要如此複雜,我將上面的Execute方法改為:
複製代碼 代碼如下:
public object Execute(ControllerBase controller, object[] parameters) {
return MethodInfo.Invoke(controller, parameters);
//return _executor(controller, parameters);
}
運行結果是完全一樣的。我相信mvc原始碼如此實現一定有其考慮,這個需要繼續研究。
最後附上一張函數調用圖,以便理解,僅供參考。圖片較大,點擊可看原圖。