濮阳杆衣贸易有限公司

主頁 > 知識(shí)庫 > ASP.NET MVC:Filter和Action的執(zhí)行介紹

ASP.NET MVC:Filter和Action的執(zhí)行介紹

熱門標(biāo)簽:蘇州銷售外呼系統(tǒng)預(yù)算 太原外呼電銷機(jī)器人費(fèi)用 東莞語音電銷機(jī)器人排名 外呼系統(tǒng)用員工身份證 使用智能電話機(jī)器人違法嗎 電話機(jī)器人廣告話術(shù) 保山電話外呼管理系統(tǒng)怎么用 淘寶地圖標(biāo)注如何做 朝陽市地圖標(biāo)注

根據(jù)controller的名字正確的實(shí)例化了一個(gè)controller對(duì)象。回到MVCHandler的BeginProcessRequest方法,可以看到,當(dāng)?shù)玫絚ontroller對(duì)象之后,首先判斷它是不是IAsyncController,如果是則會(huì)創(chuàng)建委托用來異步執(zhí)行。通常情況下,我們都是繼承自Controller類,這不是一個(gè)IAsyncController,于是會(huì)直接執(zhí)行Controller的Execute方法。Execute方法是在Controller的基類ControllerBase中定義的,這個(gè)方法除去一些安全檢查,初始化了ControllerContext(包含了ControllerBase和Request的信息),核心是調(diào)用了ExecuteCore方法,這在ControllerBase是個(gè)抽象方法,在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();
}}

這個(gè)方法比較簡(jiǎn)單,首先是加載臨時(shí)數(shù)據(jù),這僅在是child action的時(shí)候會(huì)出現(xiàn),暫不討論。接下來就是獲取action的名字,然后InvokeAction, 這里的ActionInvoker是一個(gè)ControllerActionInvoker類型的對(duì)象,我們來看它的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;}

這是一個(gè)非常核心的方法,有很多工作在這里面完成。ASP.NET MVC中有幾個(gè)以Descriptor結(jié)尾的類型,首先獲得ControllerDescriptor,這個(gè)比較簡(jiǎn)單,實(shí)際返回的是ReflectedControllerDescriptor對(duì)象。第二步實(shí)際上是調(diào)用了ReflectedControllerDescriptor的FindAction方法,獲得ActionDescriptor,ActionDescriptor最重要的屬性是一個(gè)MethodInfo,這就是當(dāng)前action name對(duì)應(yīng)的Action的方法。FindAction方法內(nèi)部實(shí)際上是調(diào)用了ActionMethodSelector的FindActionMethod來獲得MethodInfo,可以想象,這個(gè)方法將會(huì)反射controller的所有方法的名字,然后和action name匹配,實(shí)際上,ASP.NET還支持一些額外的功能,主要是: 1.通過ActionNameAttribute屬性重命名action的名字;2.支持ActionMethodSelectorAttribute對(duì)action方法進(jìn)行篩選,比如[HttpPost]之類的。下面簡(jiǎn)單看下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);
} }

這個(gè)方法是很清晰的,找到重命名之后符合的,本身名字符合的,然后所有的方法判斷是否滿足ActionMethodSelectorAttribute的條件,最后或者返回匹配的MethodInfo,或者拋出異常,或者返回null。三個(gè)步驟的實(shí)現(xiàn)并不困難,不再分析下去。
第三步是得到Filter。 FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);實(shí)際調(diào)用的是:
FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor);這里的代碼風(fēng)格和之前的不太一樣,特別喜歡用各種委托,讀代碼有點(diǎn)困難,估計(jì)不是同一個(gè)人寫的。下面的分析都直接給出實(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注冊(cè)全局filter
2. 通過屬性給Action方法或者Controller加上filter
3. Controller類本身也實(shí)現(xiàn)了IActionFilter等幾個(gè)接口。通過重寫Controller類幾個(gè)相關(guān)方法加上filter。
這三種方式就對(duì)應(yīng)了三個(gè)FilterProvider,這三個(gè)Provider的實(shí)現(xiàn)都不是很困難,不分析了。到此為止,準(zhǔn)備工作都好了,接下來就會(huì)執(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接口的時(shí)候,要表示驗(yàn)證失敗,需要在OnAuthorization方法中將參數(shù)context的Result設(shè)置為ActionResult,表示驗(yàn)證失敗后需要顯示的頁面。接下來如果驗(yàn)證失敗就會(huì)執(zhí)行context的Result,如果成功就要執(zhí)行GetParameterValues獲得Action的參數(shù),在這個(gè)方法內(nèi)部會(huì)進(jìn)行Model Binding,這也是ASP.NET的一個(gè)重要特性,另文介紹。再接下來會(huì)分別執(zhí)行InvokeActionMethodWithFilters和InvokeActionResultWithFilters,這兩個(gè)方法的結(jié)構(gòu)是類似的,只是一個(gè)是執(zhí)行Action方法和IActionFilter,一個(gè)是執(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 接受一個(gè)函數(shù)作為第一個(gè)參數(shù),這個(gè)函數(shù)的參數(shù)有兩個(gè),類型為a,b,返回類型為b,第二個(gè)參數(shù)是類型b,作為起始值,第三個(gè)參數(shù)是一個(gè)類型為a的數(shù)組,foldr的功能是依次將數(shù)組中的a 和上次調(diào)用第一個(gè)參數(shù)函數(shù)(f )的返回值作為f的兩個(gè)參數(shù)進(jìn)行調(diào)用,第一次調(diào)用f的時(shí)候用起始值。對(duì)于C#來說,用面向?qū)ο蟮姆绞奖硎?,是作為IEnummerable的一個(gè)擴(kuò)展方法實(shí)現(xiàn)的,由于C# 不能直接將函數(shù)作為函數(shù)的參數(shù)傳入,所以傳入的是委托。說起來比較拗口,看一個(gè)例子:
復(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)上來,這里對(duì)應(yīng)的類型a是IActionFilter,類型b是FuncActionExecutedContext>,初始值是continuation。假設(shè)我們有3個(gè)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這個(gè)委托,把thunk展開成上面的樣子,應(yīng)該比較清楚真正的調(diào)用順序什么樣的了。這里花了比較多的筆墨介紹了如何通過Aggregate方法構(gòu)造調(diào)用鏈,這里有一篇文章專門介紹了這個(gè),也可以參考下。想象下,如果filter的功能就是先遍歷調(diào)用f的Executing方法,然后調(diào)用Action方法,最后再依次調(diào)用f的Executed方法,那么完全可以用迭代來實(shí)現(xiàn),大可不必如此抽象復(fù)雜,關(guān)鍵是ASP.NET MVC對(duì)于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)長(zhǎng),首先就是觸發(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ā)生的時(shí)候觸發(fā)的。在InvokeAction方法中可以看到觸發(fā)它的代碼放在一個(gè)catch塊中。IExceptionFilter的觸發(fā)流程比較簡(jiǎn)單,不多做解釋了。唯一需要注意的是ExceptionHandled屬性設(shè)置為true的時(shí)候就不會(huì)拋出異常了,這個(gè)屬性在各種context下面都有,他們是的效果是一樣的。比如在OnActionExecuted方法中也可以將他設(shè)置為true,同樣不會(huì)拋出異常。這些都比較簡(jiǎn)單,不再分析其源代碼,這篇文章比較詳細(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會(huì)去調(diào)用ActionMethodDispatcher的Execute方法,這個(gè)方法如下:
復(fù)制代碼 代碼如下:

public object Execute(ControllerBase controller, object[] parameters) {
return _executor(controller, parameters);
}

此處的_executor是
delegate object ActionExecutor(ControllerBase controller, object[] parameters);_exectuor被賦值是通過一個(gè)方法,利用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)一定有其考慮,這個(gè)需要繼續(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的方法

標(biāo)簽:運(yùn)城 呼倫貝爾 潛江 阿里 洛陽 克拉瑪依 綏化 西藏

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《ASP.NET MVC:Filter和Action的執(zhí)行介紹》,本文關(guān)鍵詞  ASP.NET,MVC,Filter,和,Action,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《ASP.NET MVC:Filter和Action的執(zhí)行介紹》相關(guān)的同類信息!
  • 本頁收集關(guān)于ASP.NET MVC:Filter和Action的執(zhí)行介紹的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    许昌市| 花莲县| 东海县| 柳河县| 中江县| 古交市| 高密市| 莲花县| 荥经县| 黄龙县| 阜新| 白水县| 沿河| 肇源县| 西吉县| 南岸区| 综艺| 浦北县| 安丘市| 西安市| 邻水| 镶黄旗| 于田县| 东辽县| 天台县| 万安县| 哈巴河县| 通山县| 马公市| 砚山县| 通辽市| 七台河市| 来宾市| 钟祥市| 始兴县| 桓台县| 扎兰屯市| 游戏| 黄浦区| 鄯善县| 简阳市|