根據(jù)controller的名字正確的實(shí)例化了一個controller對象?;氐組VCHandler的BeginProcessRequest方法,可以看到,當(dāng)?shù)玫絚ontroller對象之后,首先判斷它是不是IAsyncController,如果是則會創(chuàng)建委托用來異步執(zhí)行。通常情況下,我們都是繼承自Controller類,這不是一個IAsyncController,于是會直接執(zhí)行Controller的Execute方法。Execute方法是在Controller的基類ControllerBase中定義的,這個方法除去一些安全檢查,初始化了ControllerContext(包含了ControllerBase和Request的信息),核心是調(diào)用了ExecuteCore方法,這在ControllerBase是個抽象方法,在Controller類中有實(shí)現(xiàn):
復(fù)制代碼 代碼如下:
protected override void ExecuteCore() {
PossiblyLoadTempData();
try {
string actionName = RouteData.GetRequiredString("action");
if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
HandleUnknownAction(actionName);
}
}
finally {
PossiblySaveTempData();
}}
這個方法比較簡單,首先是加載臨時數(shù)據(jù),這僅在是child action的時候會出現(xiàn),暫不討論。接下來就是獲取action的名字,然后InvokeAction, 這里的ActionInvoker是一個ControllerActionInvoker類型的對象,我們來看它的InvokeAction方法,
復(fù)制代碼 代碼如下:
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結(jié)尾的類型,首先獲得ControllerDescriptor,這個比較簡單,實(shí)際返回的是ReflectedControllerDescriptor對象。第二步實(shí)際上是調(diào)用了ReflectedControllerDescriptor的FindAction方法,獲得ActionDescriptor,ActionDescriptor最重要的屬性是一個MethodInfo,這就是當(dāng)前action name對應(yīng)的Action的方法。FindAction方法內(nèi)部實(shí)際上是調(diào)用了ActionMethodSelector的FindActionMethod來獲得MethodInfo,可以想象,這個方法將會反射controller的所有方法的名字,然后和action name匹配,實(shí)際上,ASP.NET還支持一些額外的功能,主要是: 1.通過ActionNameAttribute屬性重命名action的名字;2.支持ActionMethodSelectorAttribute對action方法進(jìn)行篩選,比如[HttpPost]之類的。下面簡單看下ActionMethodSelector的實(shí)現(xiàn),大致分為4步,首先是在構(gòu)造函數(shù)中調(diào)用了如下方法反射controller中的所有action方法:
復(fù)制代碼 代碼如下:
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。三個步驟的實(shí)現(xiàn)并不困難,不再分析下去。
第三步是得到Filter。 FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);實(shí)際調(diào)用的是:
FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor);這里的代碼風(fēng)格和之前的不太一樣,特別喜歡用各種委托,讀代碼有點(diǎn)困難,估計(jì)不是同一個人寫的。下面的分析都直接給出實(shí)際執(zhí)行的代碼。首先看下FilterProvider的構(gòu)造函數(shù):
復(fù)制代碼 代碼如下:
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類本身也實(shí)現(xiàn)了IActionFilter等幾個接口。通過重寫Controller類幾個相關(guān)方法加上filter。
這三種方式就對應(yīng)了三個FilterProvider,這三個Provider的實(shí)現(xiàn)都不是很困難,不分析了。到此為止,準(zhǔn)備工作都好了,接下來就會執(zhí)行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 |
下面看其源代碼的實(shí)現(xiàn),首先就是InvokeAuthorizationFilters:
復(fù)制代碼 代碼如下:
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;}
注意到在實(shí)現(xiàn)IAuthorizationFilter接口的時候,要表示驗(yàn)證失敗,需要在OnAuthorization方法中將參數(shù)context的Result設(shè)置為ActionResult,表示驗(yàn)證失敗后需要顯示的頁面。接下來如果驗(yàn)證失敗就會執(zhí)行context的Result,如果成功就要執(zhí)行GetParameterValues獲得Action的參數(shù),在這個方法內(nèi)部會進(jìn)行Model Binding,這也是ASP.NET的一個重要特性,另文介紹。再接下來會分別執(zhí)行InvokeActionMethodWithFilters和InvokeActionResultWithFilters,這兩個方法的結(jié)構(gòu)是類似的,只是一個是執(zhí)行Action方法和IActionFilter,一個是執(zhí)行ActionResult和IResultFilter。以InvokeActionMethodWithFilters為例分析下:
復(fù)制代碼 代碼如下:
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();
}
這段代碼有點(diǎn)函數(shù)式的風(fēng)格,不熟悉這種風(fēng)格的人看起來有點(diǎn)難以理解。 用函數(shù)式編程語言的話來說,這里的Aggregate其實(shí)就是foldr,
foldr::(a->b->b)->b->[a]->b
foldr 接受一個函數(shù)作為第一個參數(shù),這個函數(shù)的參數(shù)有兩個,類型為a,b,返回類型為b,第二個參數(shù)是類型b,作為起始值,第三個參數(shù)是一個類型為a的數(shù)組,foldr的功能是依次將數(shù)組中的a 和上次調(diào)用第一個參數(shù)函數(shù)(f )的返回值作為f的兩個參數(shù)進(jìn)行調(diào)用,第一次調(diào)用f的時候用起始值。對于C#來說,用面向?qū)ο蟮姆绞奖硎?,是作為IEnummerable的一個擴(kuò)展方法實(shí)現(xiàn)的,由于C# 不能直接將函數(shù)作為函數(shù)的參數(shù)傳入,所以傳入的是委托。說起來比較拗口,看一個例子:
復(fù)制代碼 代碼如下:
static void AggTest()
{
int[] data = { 1, 2, 3, 4 };
var res = data.Aggregate("String", (str, val) => str + val.ToString());
Console.WriteLine(res);
}
最后輸出的結(jié)果是String1234. 回到InvokeActionMethodWithFilters的實(shí)現(xiàn)上來,這里對應(yīng)的類型a是IActionFilter,類型b是FuncActionExecutedContext>,初始值是continuation。假設(shè)我們有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()之前,所有真正的代碼都沒有執(zhí)行,關(guān)鍵是構(gòu)建好了thunk這個委托,把thunk展開成上面的樣子,應(yīng)該比較清楚真正的調(diào)用順序什么樣的了。這里花了比較多的筆墨介紹了如何通過Aggregate方法構(gòu)造調(diào)用鏈,這里有一篇文章專門介紹了這個,也可以參考下。想象下,如果filter的功能就是先遍歷調(diào)用f的Executing方法,然后調(diào)用Action方法,最后再依次調(diào)用f的Executed方法,那么完全可以用迭代來實(shí)現(xiàn),大可不必如此抽象復(fù)雜,關(guān)鍵是ASP.NET MVC對于filter中異常的處理還有一些特殊之處,看下InvokeActionMethodFilter的實(shí)現(xiàn):
復(fù)制代碼 代碼如下:
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;
}
代碼有點(diǎn)長,首先就是觸發(fā)了filter的OnActionExecuting方法,這是方法的核心。接下來的重點(diǎn)是 postContext = continuation(); 最后是OnActionExecuted方法,結(jié)合上面的展開式,我們可以知道真正的調(diào)用順序?qū)⑹?
復(fù)制代碼 代碼如下:
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倒序排一下之后才是正確的執(zhí)行順序。
還有一類filter是當(dāng)異常發(fā)生的時候觸發(fā)的。在InvokeAction方法中可以看到觸發(fā)它的代碼放在一個catch塊中。IExceptionFilter的觸發(fā)流程比較簡單,不多做解釋了。唯一需要注意的是ExceptionHandled屬性設(shè)置為true的時候就不會拋出異常了,這個屬性在各種context下面都有,他們是的效果是一樣的。比如在OnActionExecuted方法中也可以將他設(shè)置為true,同樣不會拋出異常。這些都比較簡單,不再分析其源代碼,這篇文章比較詳細(xì)的介紹了filter流程中出現(xiàn)異常之后的執(zhí)行順序。
最后說下Action Method的執(zhí)行,前面我們已經(jīng)得到了methodInfo,和通過data binding獲得了參數(shù),調(diào)用Action Method應(yīng)該是萬事俱備了。asp.net mvc這邊的處理還是比較復(fù)雜的,ReflectedActionDescriptor會去調(diào)用ActionMethodDispatcher的Execute方法,這個方法如下:
復(fù)制代碼 代碼如下:
public object Execute(ControllerBase controller, object[] parameters) {
return _executor(controller, parameters);
}
此處的_executor是
delegate object ActionExecutor(ControllerBase controller, object[] parameters);_exectuor被賦值是通過一個方法,利用Expression拼出方法體、參數(shù),代碼在(ActionMethodDispatcher.cs):
static ActionExecutor GetExecutor(MethodInfo methodInfo)此處就不貼出了,比較復(fù)雜。這里讓我比較費(fèi)解的是,既然MethodInfo和parameters都有了,直接用反射就可以了,為什么還要如此復(fù)雜,我將上面的Execute方法改為:
復(fù)制代碼 代碼如下:
public object Execute(ControllerBase controller, object[] parameters) {
return MethodInfo.Invoke(controller, parameters);
//return _executor(controller, parameters);
}
運(yùn)行結(jié)果是完全一樣的。我相信mvc源代碼如此實(shí)現(xiàn)一定有其考慮,這個需要繼續(xù)研究。
最后附上一張函數(shù)調(diào)用圖,以便理解,僅供參考。圖片較大,點(diǎn)擊可看原圖。
您可能感興趣的文章:- ASP.NET MVC中URL地址傳參的兩種寫法
- 解讀ASP.NET 5 & MVC6系列教程(10):Controller與Action
- asp.net mvc-Controllerl篇 ControllerDescriptor
- 詳解ASP.NET MVC下的異步Action的定義和執(zhí)行原理
- ASP.NET MVC使用ActionFilterAttribute實(shí)現(xiàn)權(quán)限限制的方法(附demo源碼下載)
- asp.net MVC利用ActionFilterAttribute過濾關(guān)鍵字的方法
- 使用ASP.NET MVC 4 Async Action+jQuery實(shí)現(xiàn)消息通知機(jī)制的實(shí)現(xiàn)代碼
- asp.net MVC實(shí)現(xiàn)無組件上傳圖片實(shí)例介紹
- ASP.NET MVC DropDownList數(shù)據(jù)綁定及使用詳解
- ASP.NET MVC 控制器與視圖
- ASP.NET實(shí)現(xiàn)MVC中獲取當(dāng)前URL、controller及action的方法