MVC源码分析 - Action查找和过滤器的执行时机
接著上一篇, 在創建好Controller之后, 有一個 this.ExecuteCore()方法, 這部分是執行的. 那么里面具體做了些什么呢?
//ControllerBaseprotected virtual void Execute(RequestContext requestContext) {if (requestContext == null){throw new ArgumentNullException("requestContext");}if (requestContext.HttpContext == null){throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");}this.VerifyExecuteCalledOnce();
//在這里創建了控制器上下文, ControllerContextthis.Initialize(requestContext);using (ScopeStorage.CreateTransientScope()){
//加載 TempData, 創建及執行 Action, 處理 Action 返回的 ActionResult, 保存TempDatathis.ExecuteCore();} }
來看一下這里的 ExecuteCore具體是執行的那里的方法.
//System.Web.MVC.Controller protected override void ExecuteCore() {//從Session 中加載 TempData 數據this.PossiblyLoadTempData();try{
//從路由中獲取 Action 名稱string requiredString = this.RouteData.GetRequiredString("action");
//判斷部分會去執行 Action, 并處理 Action 返回的ActionResultif (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString)){this.HandleUnknownAction(requiredString);}}finally{
//保存 TempData 數據this.PossiblySaveTempData();} }
這個類應該還是蠻熟悉的吧, 我們創建的控制器類, 都會直接或者間接繼承這個類.
?
一、解析
1. PossiblyLoadTempData()
首先來看一下這個方法吧. 看看里面做了些什么
internal void PossiblyLoadTempData() {if (!base.ControllerContext.IsChildAction){base.TempData.Load(base.ControllerContext, this.TempDataProvider);} }看到這里的TempData, 感覺好熟悉吧. 我還是嘮叨一句吧.
TempData是保存在session中的, 可以用于不同Controller,不同Action,Action到View之間的傳值.
額, 要不要繼續解析一下呢, 算了, 看一下吧, 起碼看到這個數據是保存在session中的.
//TempDataDictionarypublic void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {IDictionary<string, object> dictionary = tempDataProvider.LoadTempData(controllerContext);this._data = (dictionary != null) ?
new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase) :
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);this._initialKeys = new HashSet<string>(this._data.Keys, StringComparer.OrdinalIgnoreCase);this._retainedKeys.Clear(); }
繼續看LoadTempData()方法, 真相就能大白了.
//System.Web.Mvc.SessionStateTempDataProviderpublic virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) {HttpSessionStateBase session = controllerContext.HttpContext.Session;if (session != null){Dictionary<string, object> dictionary = session["__ControllerTempData"] as Dictionary<string, object>;if (dictionary != null){session.Remove("__ControllerTempData");return dictionary;}}return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); }
從這里能看到, 我所言非虛了, 確實是存放在Session中的.
注意 : 同一個 TempData 只能被傳遞一次, 當在Session中找到TempData后, 就會將它清除掉, 下一次請求是不能再獲取到這個數據. 因為在Session中已經被清除了.
?
2.?GetRequiredString("action")
這個方法應該不需要多說了, 跟前面獲取控制器名稱的方式一樣, 只不過這里是用來獲取 Action 方法的名稱
?
3.?InvokeAction 這個是重點方法
前面既然已經獲取到 Action 方法的名字, 那么現在是不是應該去找到這個方法, 并看看是否能匹配的上呢? 匹配的時候, 用的是哪一些條件? 如果匹配的上, 又怎么執行的呢? 謎底即將揭曉.
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 = this.GetControllerDescriptor(controllerContext);
//這里調用的FindAction方法, 其實就是調用控制器描述類里面的 FindAction 方法, 但是這是一個抽象方法
//這里返回的是 Action 方法的描述信息
//默認調用的是 System.Web.Mvc.ReflectedControllerDescriptor 的 FindAction 方法(這里說的都是同步的情況下)ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName);if (actionDescriptor == null){return false;}
//獲取所有的過濾器FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor);try{
//Authorization 過濾器, 執行之后,會將結果存放在 ActionResult 類型的Result屬性中,
//如果返回結果不為空, 則不會再去執行Action里面的方法和View里面的內容了.AuthorizationContext context = this.InvokeAuthorizationFilters(controllerContext,
filters.AuthorizationFilters, actionDescriptor);if (context.Result != null){this.InvokeActionResult(controllerContext, context.Result);}else{if (controllerContext.Controller.ValidateRequest){ValidateRequest(controllerContext);}
//獲取參數的信息, 存入字典中, 以供做參數匹配IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor);
//Action 過濾器, Action方法的執行也在這里面進行ActionExecutedContext context2 = this.InvokeActionMethodWithFilters(controllerContext,
filters.ActionFilters, actionDescriptor, parameterValues);
//Result 過濾器this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, context2.Result);}}catch (ThreadAbortException){throw;}catch (Exception exception){
//錯誤信息過濾器, HandleErrorExceptionContext context3 = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception);if (!context3.ExceptionHandled){throw;}this.InvokeActionResult(controllerContext, context3.Result);}return true; }
乍一看, 里面的內容真心多啊. 還是那句話, 不要怕, 一個一個來.
3.1 FindAction - 先看一下這個方法, 是怎么找到 Action 的
//ReflectedControllerDescriptorpublic override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName) {if (controllerContext == null){throw new ArgumentNullException("controllerContext");}if (string.IsNullOrEmpty(actionName)){throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");}MethodInfo methodInfo = this._selector.FindActionMethod(controllerContext, actionName);if (methodInfo == null){return null;}
//ReflectedActionDescriptor 類是 繼承自 ActionDescriptor類的return new ReflectedActionDescriptor(methodInfo, actionName, this); }
從這里看, 是通過控制器上下文和Action方法的名稱去獲取到的.
那么進去再看一下吧.
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) {List<MethodInfo> matchingAliasedMethods = this.GetMatchingAliasedMethods(controllerContext, actionName);matchingAliasedMethods.AddRange(this.NonAliasedMethods[actionName]);List<MethodInfo> ambiguousMethods = RunSelectionFilters(controllerContext, matchingAliasedMethods);switch (ambiguousMethods.Count){case 0:return null;case 1:return ambiguousMethods[0];}throw this.CreateAmbiguousMatchException(ambiguousMethods, actionName); }3.1.1 GetMatchingAliaseMethods / NonAliaseMethods
這里可能有點讓人費解, 可能是不明白 Aliased 的意思, 翻譯過來其實是別名的意思.
這里牽涉到Action的一個功能, 就是給Action取別名. 通過 ActionNameAttribute 特性來進行.
這里的意思, 就是獲取到所有的方法, 包括有別名的和沒有別名的. 對于聲明了 NonAliasedAttribute特性的方法, 也會獲取出來.
3.1.2 RunSelectionFilters
private static List<MethodInfo> RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos) {List<MethodInfo> list = new List<MethodInfo>();List<MethodInfo> list2 = new List<MethodInfo>();using (List<MethodInfo>.Enumerator enumerator = methodInfos.GetEnumerator()){Func<ActionMethodSelectorAttribute, bool> predicate = null;MethodInfo methodInfo;while (enumerator.MoveNext()){methodInfo = enumerator.Current;ICollection<ActionMethodSelectorAttribute> actionMethodSelectorAttributes =ReflectedAttributeCache.GetActionMethodSelectorAttributes(methodInfo);if (actionMethodSelectorAttributes.Count == 0){list2.Add(methodInfo);}else{if (predicate == null){predicate = attr => attr.IsValidForRequest(controllerContext, methodInfo);}if (actionMethodSelectorAttributes.All<ActionMethodSelectorAttribute>(predicate)){list.Add(methodInfo);}}}}if (list.Count <= 0){return list2;}return list; }
這里其實是對方法進行一個過濾和一個返回優先級的問題.
Action上的Attribute如果是ActionMethodSelectorAttribute類型或者是繼承了它的, 并且該特性的 IsValidForRequest()返回的結果是true,那么就會通過篩選, 優先返回.
這里其實主要是為了過濾 AcceptVerbsAttributes 和 NonActionAttribute 的.
NonActionAttribute : 他的IsValidForRequest()返回的是false, 所以Action上有此特性的方法會被篩選掉.
AcceptVerbsAttributes ?: 同名方法, 必須聲明不同的此特性, post, get, 否則也還是會報錯.
3.2 GetFilters() - 獲取Controller 和 Action 中, 聲明的所有的 過濾器
protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {return new FilterInfo(this._getFiltersThunk(controllerContext, actionDescriptor)); }這里的_getFiltersThunk是一個Func<>()委托, 那么具體是什么方法呢?
其實這里指向的是System.Web.Mvc.FilterProviderCollection的GetFilters()方法, 別問我是怎么知道的哦.?
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {if (controllerContext == null){throw new ArgumentNullException("controllerContext");}if (actionDescriptor == null){throw new ArgumentNullException("actionDescriptor");}IEnumerable<Filter> source = (from fp in this.CombinedItemsselect fp.GetFilters(controllerContext, actionDescriptor))
.OrderBy<Filter, Filter>(filter => filter, _filterComparer);return this.RemoveDuplicates(source.Reverse<Filter>()).Reverse<Filter>(); }
看一下這里的GetFilters(), 他其實是IFilterProvider接口中的方法, 那么哪一些類實現了這個接口呢?
解析到這里, 大致已經能知道, 獲取了哪一些過濾器了.
在更進一步, 看一下 ControllerInstanceFilterProvider里面, 干了些什么.
?
public enum FilterScope {Action = 30,Controller = 20,First = 0,Global = 10,Last = 100 }public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {if (controllerContext.Controller == null){yield break;}yield return new Filter(controllerContext.Controller, FilterScope.First, -2147483648); }?
3.3?InvokeAuthorizationFilters() -?Authorization過濾器, 控制器的部分下一篇會詳細描述
protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext,IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor) {AuthorizationContext filterContext = new AuthorizationContext(controllerContext, actionDescriptor);foreach (IAuthorizationFilter filter in filters){
//遍歷執行控制器方法filter.OnAuthorization(filterContext);if (filterContext.Result != null){return filterContext;}}return filterContext; }
3.4?InvokeActionMethodWithFilters() - Action 過濾器, 這里面其實會執行兩個過濾器 : OnActionExecuting / OnActionExecuted
我們其實都知道, 這兩個過濾器中間, 還少了一個東西, 就是 Action 方法的執行, 那么在執行這個方法的過程之中, 就會去執行 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> seed = () => new ActionExecutedContext(controllerContext, actionDescriptor, false, null)
{ Result = this.InvokeActionMethod(controllerContext, actionDescriptor, parameters) };return filters.Reverse<IActionFilter>().Aggregate<IActionFilter,
Func<ActionExecutedContext>>(seed, (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next))(); }
| 方法 | 參數 | 描述 |
| OnActionExecuting | ActionExecutingContext | 在行為方法執行前執行 |
| OnActionExecuted | ActionExecutedContext | 在行為方法執行后執行 |
| OnResultExecuting | ResultExecutingContext | 在行為方法返回前執行 |
| OnResultExecuted | ResultExecutedContext | 在行為方法返回后執行 ? |
過濾器這部分內容會在后面的篇章做出詳細解釋.
3.5?InvokeActionResultWithFilters() - Result 過濾器, 這里面也會執行兩個過濾器 : OnResultExecuting / OnResultExecuted
這里同上面是一樣的, 這兩個過濾器中間, 少了一個 View 的執行, 也就是說, 在執行這個方法的時候, 會動態執行 View 頁面方法.
protected virtual ResultExecutedContext InvokeActionResultWithFilters(ControllerContext controllerContext,IList<IResultFilter> filters, ActionResult actionResult) {ResultExecutingContext preContext = new ResultExecutingContext(controllerContext, actionResult);Func<ResultExecutedContext> seed = delegate {this.InvokeActionResult(controllerContext, actionResult);return new ResultExecutedContext(controllerContext, actionResult, false, null);};return filters.Reverse<IResultFilter>().Aggregate<IResultFilter,
Func<ResultExecutedContext>>(seed, (next, filter) => () => InvokeActionResultFilter(filter, preContext, next))(); }
3.6 ?InvokeActionResult()
注意到這里還有一個方法, 就是當過濾器不通過的時候執行的. 來看一下吧.
protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) {actionResult.ExecuteResult(controllerContext); }從這里可能還不能比較直觀的知道, 在做什么, 但是我貼一張圖, 應該就能比較清楚了
?
4.?PossiblySaveTempData()
internal void PossiblySaveTempData() {if (!base.ControllerContext.IsChildAction){base.TempData.Save(base.ControllerContext, this.TempDataProvider);} }這里是保存 TempData數據
?
二、擴展或功能
1. 給方法取別名
我這里使用的是前面講路由的例子, 目錄結構如下圖:
[ActionName("Get")] public ActionResult GetA(int id ) {return View(id); }如果沒有這個別名, 我這里肯定是找不到這個視圖的, 請求的路徑也應該是 Home/GetA的方式. 那么下面看一下結果:
從這里的結果來看, 我的請求路徑是 Home/Get 方式
?那如果我的別名和我的另一個方法相同, 那么就算我的參數并不一樣, 但是會報錯的. MVC 不知道該用哪一個方法了.
目錄已同步
轉載于:https://www.cnblogs.com/elvinle/p/6293071.html
總結
以上是生活随笔為你收集整理的MVC源码分析 - Action查找和过滤器的执行时机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: placeholder这个属性 inpu
- 下一篇: 位域