根據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);
}
IDictionarystring, 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) {
ListMethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
ListMethodInfo> 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, IListIAuthorizationFilter> 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, IListIActionFilter> filters, ActionDescriptor actionDescriptor, IDictionarystring, object> parameters) {
ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
FuncActionExecutedContext> 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
FuncActionExecutedContext> 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是FuncActionExecutedContext>,初始值是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, FuncActionExecutedContext> 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源代碼如此實現一定有其考慮,這個需要繼續研究。
最后附上一張函數調用圖,以便理解,僅供參考。圖片較大,點擊可看原圖。

您可能感興趣的文章:- ASP.NET MVC中URL地址傳參的兩種寫法
- 解讀ASP.NET 5 & MVC6系列教程(10):Controller與Action
- asp.net mvc-Controllerl篇 ControllerDescriptor
- 詳解ASP.NET MVC下的異步Action的定義和執行原理
- ASP.NET MVC使用ActionFilterAttribute實現權限限制的方法(附demo源碼下載)
- asp.net MVC利用ActionFilterAttribute過濾關鍵字的方法
- 使用ASP.NET MVC 4 Async Action+jQuery實現消息通知機制的實現代碼
- asp.net MVC實現無組件上傳圖片實例介紹
- ASP.NET MVC DropDownList數據綁定及使用詳解
- ASP.NET MVC 控制器與視圖
- ASP.NET實現MVC中獲取當前URL、controller及action的方法