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;几乎所有的电视机、影碟机类产品都有这个接口。它并不是专门为…

Retrofit2源码解析——网络调用流程(下)

Retrofit2源码解析系列 Retrofit2源码解析(一)Retrofit2源码解析——网络调用流程(上)本文基于Retrofit2的2.4.0版本 implementation com.squareup.retrofit2:retrofit:2.4.0 复制代码上次我们分析到网络请求是通过OkHttpCall类来完成的&#xff0c;下面我们就来分析下OkHttpCa…

spring EL 实现ref的效果

之前学习basic的时候有个疑问就是不知道如何实现bean中引用其他的bean的属性&#xff0c;当时是用ref来实现对其他bean的引用&#xff0c;但是ref必需引用的是一个常量。所以这种方式来实现对其他bean中的属性的引用是不合理的。 当我看到Spring Expression Language时发现原来…

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&…

vue部署问题

history模式配置后刷新404的解决办法! 第一种 nginx配置 在usr/local/nginx/conf/vhost 下 域名.conf配置文件修改或添加 第一种方案server {##在server下添加或在location里面添加以下代码location / {if (!-e $request_filename) {rewrite ^(.*)$ /index.html?s$1 last…

位域

有些信息在存储时&#xff0c;并不需要占用一个完整的字节&#xff0c; 而只需占几个或一个二进制位。例如在存放一个开关量时&#xff0c;只有0和1 两种状态&#xff0c; 用一位二进位即可。为了节省存储空间&#xff0c;并使处理简便&#xff0c;C语言又提供了一种数据结构&a…

数字后端——ECO

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

Uncaught TypeError: Cannot read property 'length' of null错误怎么处理?

Uncaught TypeError: Cannot read property length of null 错误怎么处理&#xff1f; 1.可能是返回的datagrid数据格式有问题&#xff0c;比如{"total":0,"rows":null}&#xff0c;改为{"total":0,"rows":"[]"}就可以了 if…

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

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

tracert路由检测命令使用方法

很多客户网站无法访问的时候都会第一时间怀疑是虚拟主机有问题了&#xff0c;其实大多时候网站无法访问和很多因素相关&#xff0c;包括自己的网络、计算机设置、省际路由等等&#xff1b; 那么这里我就简单讲下如何利用DOS下的命令检测你的计算机到服务器之间的路由是否通畅&a…

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

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

vue双向数据绑定的原理

有关双向数据绑定的原理 最近两次面试的时候&#xff0c;被问到了vue中双向数据绑定的原理&#xff0c;因为初学不精&#xff0c;只是使用而没有深入研究&#xff0c;所以答不出来。之后就在网上查找了别人写的博客&#xff0c;学习一下。 下面是博客园一篇博客&#xff0c;以及…

求职网站总结

最近忙着要找份工作。毕业半年多就辞职&#xff0c;也是尴尬。 这里记录一些求职网站和找工作的一些经验。主要参考了三个知乎问题&#xff1a;怎么在互联网上找工作&#xff1f;&#xff0c;招聘网站&#xff0c;哪个靠谱&#xff1f;和哪个求职网站&#xff08;app&#xff0…

FTP命令解析

FTP命令详解FTP命令是Internet用户使用最频繁的命令之一&#xff0c;不论是在DOS还是UNIX操作系统下使用FTP&#xff0c;都会遇到大量的FTP内部命令。熟悉并灵活应用FTP的内部命令&#xff0c;可以大大方便使用者&#xff0c;并收到事半功倍之效。   FTP的命令行格式为&…

深入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…