MVC源码分析 - Action查找和过滤器的执行时机

接着上一篇, 在创建好Controller之后, 有一个 this.ExecuteCore()方法, 这部分是执行的. 那么里面具体做了些什么呢?

//ControllerBase
protected
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();
   //在这里创建了控制器上下文, ControllerContext
this.Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) {
     //加载 TempData, 创建及执行 Action, 处理 Action 返回的 ActionResult, 保存TempData
this.ExecuteCore(); } }

来看一下这里的 ExecuteCore具体是执行的那里的方法.

//System.Web.MVC.Controller
protected override void ExecuteCore()
{
   //从Session 中加载 TempData 数据
this.PossiblyLoadTempData(); try {
     //从路由中获取 Action 名称
string requiredString = this.RouteData.GetRequiredString("action");
     //判断部分会去执行 Action, 并处理 Action 返回的ActionResult
if (!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中的.

//TempDataDictionary
public
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.SessionStateTempDataProvider
public
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) {
     //错误信息过滤器, HandleError ExceptionContext context3
= this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception); if (!context3.ExceptionHandled) { throw; } this.InvokeActionResult(controllerContext, context3.Result); } return true; }

乍一看, 里面的内容真心多啊. 还是那句话, 不要怕, 一个一个来.

3.1 FindAction - 先看一下这个方法, 是怎么找到 Action 的

//ReflectedControllerDescriptor
public
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.CombinedItems 
      select 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 不知道该用哪一个方法了.

目录已同步

posted @ 2017-01-17 16:04  Sniper_ZL  阅读(1052)  评论(0编辑  收藏  举报