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){
     //错误信息过滤器, HandleErrorExceptionContext 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 不知道该用哪一个方法了.

目录已同步

转载于:https://www.cnblogs.com/elvinle/p/6293071.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/253534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CCIE-MPLS基础篇-实验手册

又一部前期JUSTECH&#xff08;南京捷式泰&#xff09;工程师职业发展系列丛书完整拷贝。 MPLS&#xff08;Multi-Protocol Label Switching&#xff09; 目录 1&#xff1a;MPLS 基础实验.... 3 1.1实验拓扑... 3 1.2实验需求&#xff1a;... 3 1.3实验步骤... 3 1.4校验…

RCA/BNC接口

RCA接口&#xff08;消费类市场&#xff09; RCA 是Radio Corporation of American的缩写词&#xff0c;因为RCA接头由这家公司发明的。RCA俗称莲花插座&#xff0c;又叫AV端子&#xff0c;也称AV 接口&#xff0c;几乎所有的电视机、影碟机类产品都有这个接口。它并不是专门为…

2021手机CIS技术趋势总结

手机摄像头CIS&#xff08;CMOS图像传感器&#xff09;自从突破1亿像素以后&#xff0c;再谈像素数量增大&#xff0c;似乎已经很难让市场产生激烈反应了。这两年电子工程专辑对于手机摄像头CIS&#xff0c;以及更多领域不同类型的图像/视觉传感器&#xff08;如ToF、基于事件的…

关于Unity中NGUI的背包实现之Scrollview(基于Camera)

基于UIPanel的scrollview实现方式在移动设备上的性能不如基于camera的方式。因为UIPanel的scrollview实现方式要渲染很多的道具图&#xff0c;性能自然就降低了。如果是用第二个摄像机camera的方式&#xff0c;物体并没有动&#xff0c;只是拖动第二个摄像机摄像机&#xff0c;…

YUV422/420 format

(在本文中&#xff0c;U 一词相当于 Cb&#xff0c;V 一词相当于 Cr。) YUV422 format as shown below 4:2:2 表示 2:1 的水平取样&#xff0c;没有垂直下采样 YUV420 format as shown below4:2:0 表示 2:1 的水平取样&#xff0c;2:1 的垂直下采样. YUV4:2:0并不是说只有U&…

数字后端——ECO

目录 一、概述 二、ECO分类 1、按时间节点 1&#xff09;流片前的ECO 2&#xff09;流片过程的ECO 3&#xff09;流片后的ECO 2、按网表是否改变 1&#xff09;功能ECO 2&#xff09;时序ECO 三、ECO处理内容 1、设计规则违例 1&#xff09;提升标准单元驱动力 2…

电视百科常识 九大视频接口全接触

1 射频 天线和模拟闭路连接电视机就是采用射频&#xff08;RF&#xff09;接口。作为最常见的视频连接方式&#xff0c;它可同时传输模拟视频以及音频信号。RF接口传输的是视频和音频混合编码后的信号&#xff0c;显示设备的电路将混合编码信号进行一系列分离、解码在输出成像。…

数字后端——物理单元介绍

物理单元&#xff08; physical cell&#xff09;指没有逻辑功能但是具有物理实现功能的标准单元&#xff0c; 用于抑制芯片生产过程中的各类物理效应&#xff0c; 保证芯片生产后能够正常工作 。硬核位置确 定后&#xff0c;需要插入物理单元消除影响芯片工作的物 效应&#x…

深入Java内存模型

你可以在网上找到一大堆资料让你了解JMM是什么东西&#xff0c;但大多在你看完后仍然会有很多疑问。happen-before是怎么工作的呢&#xff1f;用volatile会导致缓存的丢弃吗&#xff1f;为什么我们从一开始就需要内存模型&#xff1f; 通过这篇文章&#xff0c;读者可以学习到足…

Matlab 使用GPU加速 转载

在matlab中使用GPU加速&#xff0c;来加速矩阵运算。 首先如前面所说&#xff0c;并不是所有GPU都能在maltab中进行加速的&#xff0c;貌似只有NVDIA的显卡可以吧。 硬件&#xff1a;GeForce GTX 980 软件&#xff1a;Matlab 2015a &#xff08;Matlab 2012以后的版本才带有GP…

数字后端——可制造性设计

随着集成电路制造工艺技术的迅速发展&#xff0c;集成电路集成度迅速攀升&#xff0c;制造流程及工艺步骤日趋复杂&#xff0c;工艺尺寸也在不断缩小。集成电路可制造性设计&#xff08;Design For Manufacturability,DFM&#xff09; 以直接提升集成电路芯片的良品率及降低芯片…

Cloudstack安装(二)

Cloudstack安装 官方文档参考&#xff1a; http://docs.cloudstack.apache.org/projects/cloudstack-installation/en/4.9/qig.html#environment Cloudstack主要分Management和Agent两部分。 系统版本&#xff1a;CentOS 6.8 Management&#xff1a; cpu1&#xff0c;ram 2048M…

Pycharm 输出中文或打印中文乱码现象的解决办法

转载地址&#xff1a;https://www.cnblogs.com/Bro-Young/p/5920884.html 1. 确保文件开头加上以下代码&#xff1a; 1 # -*- coding:utf-8 -*- 还可以加上 1 import sys 2 reload(sys) 3 sys.setdefaultencoding(utf-8) 确保以下。 如果还是没有解决中文乱码&#xff0c;那么进…

计算机系统结构——概述

计算机的实现包括两个方面&#xff1a;组成和硬件。组成一词包含了计算机设计的高阶内容&#xff0c;例如存储器系统&#xff0c;存储器互连&#xff0c;设计内部处理器 CPU &#xff08;中央处理器——算术、逻辑、分支和数据传送功能都在内部实现&#xff09;。有时也用微体系…

全景图像拼接——基本流程

图像拼接技术是数字图像处理技术一个重要的研究方向,它即是将两幅或多幅相互有部分重叠的场景照片拼接成具有超宽视角、与原始图像接近且失真小、没有明显缝合线的高分辨率图像。可以很好地解决广角镜、鱼眼镜头等全景图获取设备的不足。如下图: 图像拼接产生的图像不…

WPF 带CheckBox、图标的TreeView

WPF 带CheckBox、图标的TreeView 在WPF实际项目开发的时候&#xff0c;经常会用到带CheckBox的TreeView&#xff0c;虽然微软在WPF的TreeView中没有提供该功能&#xff0c;但是微软在WPF中提供强大的ItemTemplate模板功能和自定义样式&#xff0c;那我们可以自己写一个这样的控…

win32框架,GDI图形编程写一个HelloWorld游戏_c语言

1.如图&#xff0c;实现功能: Hello World!字符串跟随鼠标移动鼠标左击Hello World!颜色为红色鼠标右击Hello World!颜色为蓝色鼠标滚轮滚动改变Hello World!颜色的RGB中的G值 2.实现工具: vs20133.实现步骤: 新建一个win32项目 如图,看到HelloWorldGame.cpp中 _tWinMain()的函…

全景图像拼接——图像融合

图像融合技术就是将配准过后的图像融合成一幅宽视角、大场景的图像。但由于图像采集过程中各种因素的影响,例如光照、角度、距离等,从而导致图像间的光照不均匀、颜色上不连续。 经过配准以后,参考图像和输入图像已经在同一个坐标系下,如果只是取某一幅图像的信息或者简单地…

极详细的ECC讲解 -OOB与ECC

http://blog.csdn.net/dongzhichen/article/details/8249228 详细的ECC讲解 -OOB与ECC 在网络编程中 OOB&#xff08;out of band&#xff09;带外数据 在MTD设备中 OOB 如下所示&#xff1a; http://www.cnblogs.com/bcxx_qin/archive/2009/06/11/1501271.html 极详细的ECC…

前端进阶(8) - 前端开发需要了解的工具集合:webpack, eslint, prettier, ...

前端开发需要了解的工具集合&#xff1a;webpack, eslint, prettier, ... 前端开发需要了解的一些工具&#xff0c;这些工具能够帮助你在项目开发中事半功倍。 1. nrm: npm registry 管理器 registry: npm 远程仓库的地址。 由于众所周知的原因&#xff0c;npm 官方仓库在国内特…