白话学习MVC(八)Action的执行二

简介:

一、概述

  上篇博文《白话学习MVC(七)Action的执行一》介绍了ASP.NET MVC中Action的执行的简要流程,并且对TempData的运行机制进行了详细的分析,本篇来分析上一篇中遗留的【3-2、ActionInvoker.InvokeAction(ControllerContext, actionName)】部分的内容,其中包含了Action的执行、过滤器的执行、View的呈现(下节介绍)。

复制代码
public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter
    {protected override void ExecuteCore()
        {
            //获取上次处理过程中没有被使用的TempData
            PossiblyLoadTempData();
            try
            {
                //从路由数据中获取请求的Action的名字
                string actionName = RouteData.GetRequiredString("action");
                if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
                {
                    HandleUnknownAction(actionName);
                }
            }
            finally
            {
                //将TempData保存到Session中。等待之后将Session的key【__ControllerTempData】发送到响应流中!
                PossiblySaveTempData();
            }
        }
    }    
复制代码

 

二、详细分析

  概述中的红色字体部分,也就是我们上一节中遗留的代码段,它实现了Action的执行。现在我们就来通过MVC源代码分析此段代码所涉及的所有部分。

复制代码
    public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer
    {
        private IActionInvoker _actionInvoker;
        
        public IActionInvoker ActionInvoker
        {
            get
            {
                //ActionInvoker的InvokeAction方法就是执行Action的调用。
                //可见,此处又有一个扩展点,设置自定义的ActionInvoker(即:在激活Controller后,执行该控制器实例的ActionInvoker属性,为属性赋值即可)。
                if (_actionInvoker == null)
                {    
                    _actionInvoker = CreateActionInvoker();
                }
                return _actionInvoker;
            }
            set { _actionInvoker = value; }
        }
    
        protected override void ExecuteCore()
        {
            PossiblyLoadTempData();
            try
            {
                //从路由数据中获取请求的Action的名字(路由系统从请求地址中获取)
                string actionName = RouteData.GetRequiredString("action");
                //ActionInvoker是Controller类中的一个属性,该属性默认返回的是一个AsyncControllerActionInvoker对象
                if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
                {
                    HandleUnknownAction(actionName);
                }
            }
            finally
            {
                PossiblySaveTempData();
            }
        }
        
        protected virtual IActionInvoker CreateActionInvoker()
        {//对于Resolver,只能根据类型反射创建实例,接口和抽象类都是返回null,所以下面的代码返回的是一个AsyncControllerActionInvoker对象!(MVC3中是直接返回一个ControllerActionInvoker)
            //AsyncControllerActionInvoker不只是异步的,他还包括了同步。因为他继承自ControllerActionInvoker类,并实现了IAsyncActionInvoker接口。
            //这里就有疑问了,既然接口和抽象类都不能创建实例且返回null,那为什么还以接口为参数呢?
            return Resolver.GetService<IAsyncActionInvoker>() ?? Resolver.GetService<IActionInvoker>() ?? new AsyncControllerActionInvoker();
        }
    }
复制代码

   上述代码中,红色字体部分中的ActionInvoker是Controller类的一个属性,该属性返回的是私有IActionInvoker类型的字段_actionInvoker的值,如果_actionInvoker不等于null,则返回字段_actionInvoker的值,否则创建一个 AsyncControllerAtionInvoker对象赋值给_actionInvoker字段并返回。所以,在我们没有设置自定义ActionInvoker时,默认这个ActionInvoker是一个AsyncControllerActonInvoker对象。即:执行AsyncControllerActonInvoker对象的InvokeAction方法来完成Action的执行!

扩展:此处我们可以创建一个自定义的ActionInvoker,然后使用自定义的ActionInvoker来实现Action的执行!
  1、创建自定义一个ActionInvoker(实现IActionInvoker接口的类或者直接继承AsyncControllerActonInvoker类)。
  2、创建好自定义的ActionInvoker之后,就需要将我们的ActionInvoker设置到系统中,就是通过请求的控制器HomeController的基类Controller的这个ActionInvoker属性来进行设置。所以,我们就需要在HomeController被激活时,直接执行该控制器实例的ActionInvoker属性来设置,而控制器的激活是在一个ControllerActivator的Create方法中完成的,ControllerActivator的选择又是ControllerFactory来做的!

  Global.asax
  MyControllerActivator.cs
  MyActionInvoker.cs

   上面指出两部分内容,一、在默认情况下的ActionInvoker(AsyncControllerActonInvoker);二、使用自定义ActionInvoker。我们所提到的ActionInvoker都是泛指实现了IActionInvoker接口的类,而上述两个中情况【默认ActionInvoker(AsyncControllerActonInvoker)】和【自定义ActionInvoker】便是实现了IActionInvoker接口,并实现了该接口中唯一的一个方法InvokeAction,而实现的这个方法中包含了对Action执行的所有操作,下面就来看看默认情况下ActionInvoker(AsyncControllerActonInvoker)的InvokeAction方法中是如何定义的!其实,自定义ActionInvoker的InvokeAction方法也是仿照AsyncControllerActonInvoker类来实现的。

  AsyncControllerActionInvoker
复制代码
public class ControllerActionInvoker : IActionInvoker
{
    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        //ControllerDescriptor封装描述控制器的信息,如控制器的名称、类型和操作。
        ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
        //FindAction方法:找到要执行的那么Action,并将该Action的相关信息封装在ActionDescriptor中。
        ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
        if (actionDescriptor != null)
        {
             //GetFilters方法:获取应用在Action上的所有过滤器,并封装到一个FilterInfo对象中。
            FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

            try
            {
                //Authorize授权过滤器需要实现IAuthorizationFilter接口,该接口有一个方法:OnAuthorization
                
   //循环执行应用在Actio上所有Authorize授权过滤器的OnAuthorization方法,定义如果不满足过滤器条件,则需要创建一个ActionResult复制给Result属性
   //AuthorizeAttribute是MVC封装好的一个授权过滤器,从Cookie中获取信息,检查是否授权成功,可参考定义自己的授权管理器
                AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                if (authContext.Result != null)
                {
                    //没有通过Authorize授权过滤器,直接根据自定义的ActionResult进行View的呈现!
//View的呈现(下一节介绍)
InvokeActionResult(controllerContext, authContext.Result); } else { //ValidateRequest,该值指示是否为此请求启用请求验证 //是否对请求必须验证,默认为true,该属性定义在ControllerBase类中 if (controllerContext.Controller.ValidateRequest) { //ValidateRequest应该是检查XSS威胁之类的,在模型绑定请求中获取值前进行处理。 ValidateRequest(controllerContext); } //获取Action方法参数的值(模型绑定) IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); //执行【方法过滤器】(实现IActionFilter接口)并执行Action内代码 ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); //执行【结果过滤器】(实现IResultFilter接口),再做View的呈现。 InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result); } } catch (ThreadAbortException) { throw; } catch (Exception ex) { //执行异常过滤器 ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex); if (!exceptionContext.ExceptionHandled) { throw; }
//根据异常过滤器中定义的ActionResult进行View的呈现 InvokeActionResult(controllerContext, exceptionContext.Result); }
return true; } // notify controller that no method matched return false; } }
复制代码

  上述代码中已添加了详细的注释,大致流程为:首先,根据【控制器信息】和【Action的Name】从被请求控制器的众多Action中找到要访问的Action,然后再执行应用在Action上的过滤器,最后根据ActionResult再进行View的呈现(下一节介绍)。

 扩展:此处ControllerActionInvoker是MVC4中的,MVC5中新添加Authorizetion过滤器,并且这个过滤器的执行模式和Authorizetion过滤器是一样。对于Authentic过滤器,它需要实现IAuthenticationFilter接口,该接口中有两个方法:OnAuthentication和OnAuthenticationChallenge,执行顺序为:【Authentic过滤器的OnAuthentication方法】—>【Action过滤器的执行】—>【Action内代码的执行】—>【Authentic过滤器的OnAuthenticationChallenge方法】—>【Result过滤器的执行】—>【View的呈现】,所以就目前看来,通过这个过滤器也就可以在Action执行前且View呈现之前进行一些操作,从而可增强扩展性!

  MVC5:ControllerActionInvoker

 

InvokeAction方法解析

1、ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);

  在控制器HomeController对象的所有方法中,找到当前请求的Action(控制器对象的一个方法)。

  ControllerActionInvoker
  ReflectedControllerDescriptor
  ActionMethodSelector

  上述代码中,其实就是通过HomeController实例利用反射获取到所有的方法(包括父类中的方法),然后就是进行筛选,首先通过判断得到的方法是否是Controller类实例的方法,从而将HomeController父类中的方法过滤掉;之后再根据方法的名字来做判断,从而将方法名字不是请求的actionName的方法过滤掉;再之后判断应用在Action方法上的特性是否和客户端使用的 HTTP 数据传输方法一致,将不符合Http数据传输方法的Action过滤掉;再再之后,当符合条件的Action方法只有一个时,就将该Action方法返回,即:得到了指定的Action。最后将得到的Action方法封装到一个继承自ActionDescriptor类的ReflectedActionDescriptor对象中。

扩展:由上面介绍可知,其实对Action方法的查找和筛选都是在ActionMethodSelector中进行的,MVC5中的ActionMethodSelector虽然大体上流程是和MVC4相同的,但是具体实现上还是有点差异,有兴趣的可以看一下。

  MVC5:ActionMethodSelector

 

 2、FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

  获取应用在Action方法上的所有过滤器,并将封装到一个FilterInfo对象中。这些过滤器有:ActionFilter、AuthorizationFilter、ExceptionFilter、ResultFilter,另外在MVC5中又新添加了一个AuthenticationFilter过滤器。

  ControllerActionInvoker
  FilterProviders
  FilterProviderCollection
  MultiServiceResolver
  GlobalFilterCollection
  FilterAttributeFilterProvider
  ControllerInstanceFilterProvider
  FilterInfo

  上述代码中,实现了获取过滤器,而过滤器可以通过4种方式添加:1、Global.asax中的RegisterGlobalFilters方法,Scope=10;2、在控制器HomeController上以特性的方法添加,Scope=20;3、在Action上以特性的方式添加,Scope=30;4、控制器HomeController本身也是过滤器,它实现了各过滤器接口,Scope=0;
  针对以上的4中添加方法,【1】直接通过GlobalFilterCollection集合来获取,应为GlobalFilterCollection实现了IEnumerable接口、【2】【3】通过FilterAttributeFilterProvider对象的GetFilters方法来获取、【4】通过ControllerInstanceFilterProvider对象的GetFilters方法来获取。
  所以,整个流程为:遍历执行各【过滤器的提供器】的GetFilters方法,从而得到所有过滤器且过滤器按照Scope值从小到大排列,然后再从后向前执行来对不允许重复使用的过滤器进行去重(只保留Scope值大的过滤器),如果允许重复使用的话(AllowMutiple=true),表示允许重复使用该过滤器,则不执行去重。最终将得到的过滤器按照过滤器类型(按接口不同)分类封装到FilterInfo对象中。
更正:
FilterProviderCollection类的AllowMultiple方法中【if(mvcFilter==null){true}】,也表示该过滤器为控制器本身。因为Controller类只实现了过滤器接口,而没有实现IMvcFilter接口或继承实现了IMvcFilter接口的类。

 扩展:如有兴趣可以看一下MVC5中过获取过滤器代码

  MVC5:FilterProviderCollection
  MVC5:MultiServiceResolver

 

 3、AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);

   执行Authorize授权过滤器,其实就是执过滤器的OnAuthorization方法。AuthorizeAttribute是一个MVC的授权过滤器,可参考定义自己的授权过滤器。授权过滤器本质上是去读取cookie,检查cookie中相应的值是否和授权过滤器中设置的一致(可以用来做登录之后才能访问某页面的功能)。

  ControllerActionInvoker

 

  上述代码中,遍历所有的Authorize授权过滤器并执行其OnAuthorization方法,在OnAuthorization方法中,如果请求不满足条件,则创建一个ActionResult对象并赋值给AuthorizationContext对象的Result属性,之后直接使用该ActionResult进行View的呈现。
  在MVC中,AuthorizeAttribute是微软定义的一个授权过滤器,它的OnAuthorization方法中规定,如果不满足条件的话,就跳转到登录页面(在WebConfig文件中配置)。

 

4、IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);

  模型绑定,获取Action方法参数对象的实参。详细请看:白话学习MVC(六)模型绑定

 

 5、ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);

  执行【方法过滤器】(实现IActionFilter接口)并执行Action方法内的代码

  ControllerActionInvoker

  上述代码,首先循环执行所有Action过滤器的ActionExecuting方法,然后执行Action方法内的代码,最后再循环执行所有的Action过滤器的ActionExecuted方法。此过程的过滤器中,如果不满足过滤器的要求,则直接利用自定义的ActionResult对象进行View的呈现!
在执行Action方法内部代码时,那些返回值:return Content()、return View()、return Json()等,其实都是执行Controller类的方法,这些方法内创建继承自ActionResult类的ContentResult、ViewResult、JsonResult的对象并返回。

  Controller

 

补充:过滤器的ActionExecuting方法和ActionExecuted方法的执行是按照一条龙的顺序执行的。


此图摘自:http://www.cnblogs.com/artech/archive/2012/08/06/action-filter.html

重要:InvokeActionMethodWithFilters方法中的那句碉堡的代码实现了一条龙的方式去【OnActionExecuting】和【OnActionExecuted】方法,必须要好好学习下!!!

 

6、InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);

  执行【结果过滤器】(实现IResultFilter接口),再做View的呈现。

  ControllerActionInvoker

   上述代码中,先循环执行所有的Result过滤器的OnResultExecuting方法,然后执行View的呈现,再循环执行所有的Result过滤器的OnResultExecuted方法。执行流程也是一条龙的方方式!

   以上所有就是Action执行的全部,如果不符之处,请指正!由以上的执行可知【View的呈现】是通过ControllerActionInvoker类的InvokeActionResult方法来实现的,下一篇就来详细分析View的呈现相关的知识!

 



本文转自武沛齐博客园博客,原文链接:http://www.cnblogs.com/wupeiqi/p/3405170.html,如需转载请自行联系原作者

目录
相关文章
|
索引 Python
Pandas 高级教程——高级时间序列分析
Pandas 高级教程——高级时间序列分析
597 4
|
存储 关系型数据库 MySQL
使用 MHA 和 HAProxy 部署高可用 MySQL
使用 MHA 和 HAProxy 部署高可用 MySQL
|
JSON 安全 网络安全
Python 常用第三方库 urllib3使用
`urllib3`是线程安全的HTTP客户端库,支持连接池管理、SSL/TLS验证、HTTP/SOCKS代理。要安装它,使用`pip install urllib3`。发送HTTP请求涉及创建`PoolManager`实例并调用`request()`方法。HTTPResponse对象有status、headers和data属性。可以解码响应内容,处理JSON数据。`request()`方法接受`method`、`url`等参数,可定制请求头、查询字符串、表单数据或JSON数据,并设置超时时间。HTTPS请求默认校验证书,可通过`cert_reqs`参数禁用此功能。
|
11月前
|
应用服务中间件 Apache Windows
免安装版的Tomcat注册为windows服务
免安装版的Tomcat注册为windows服务
321 3
|
11月前
|
存储 API Apache
解密 parquet 文件,以及如何用 Python 去处理它(二)
解密 parquet 文件,以及如何用 Python 去处理它(二)
477 2
|
11月前
|
存储 Kubernetes 监控
深度解析Kubernetes在微服务架构中的应用与优化
【10月更文挑战第18天】深度解析Kubernetes在微服务架构中的应用与优化
401 0
|
11月前
|
JavaScript 前端开发 开发者
原型链深入解析:JavaScript中的核心机制
【10月更文挑战第13天】原型链深入解析:JavaScript中的核心机制
247 0
|
Kubernetes Cloud Native 关系型数据库
k8s 部署polardb-x集群
k8s 部署polardb-x集群
655 11
|
前端开发 C++
使用 Vite 创建 React+TS+SW 项目并整合 AntDesign 、Scss 等组件或插件
本文记录了如何使用Vite创建一个React+TypeScript+Service Workers(SW)项目,并整合AntDesign组件库和Scss等插件,包括项目的创建、配置问题解决、AntDesign和Scss的整合方法。
791 1
|
存储 数据可视化 数据处理
`geopandas`是一个开源项目,它为Python提供了地理空间数据处理的能力。它基于`pandas`库,并扩展了其对地理空间数据(如点、线、多边形等)的支持。`GeoDataFrame`是`geopandas`中的核心数据结构,它类似于`pandas`的`DataFrame`,但包含了一个额外的地理列(通常是`geometry`列),用于存储地理空间数据。
`geopandas`是一个开源项目,它为Python提供了地理空间数据处理的能力。它基于`pandas`库,并扩展了其对地理空间数据(如点、线、多边形等)的支持。`GeoDataFrame`是`geopandas`中的核心数据结构,它类似于`pandas`的`DataFrame`,但包含了一个额外的地理列(通常是`geometry`列),用于存储地理空间数据。