前言

在上一篇文章中,我们已经知道了在 ASP.NET Core MVC 中如何发现一个 Action,那么在发现了Action之后,就是 Action 的一个调用过程,也就是整个 Action 执行的生命周期,那么本文我们就来一起看一下Action是怎么激活并且执行的吧。

Getting Started

还是从 MvcRouteHandlerRouteAsync()开始说起,在上一篇的结尾中,我们已经拿到了 actionDescriptor 这个对象,接着,MVC 会把 actionDescriptorrouteData 已经 HttpContext 对象一起包装成为一个 ActionContext 上下文对象。

ActionContext:

public class ActionContext
{public ActionDescriptor ActionDescriptor {get; set;}public HttpContext HttpContext {get; set;}public ModelStateDictionary ModelState {get;}public RouteData RouteData {get; set;}
}

在有了 ActionContext 之后,接下来就是创建 ControllerActionInvoker 的过程,MVC 会通过一个工厂(ActionInvokerFactory)来创建 Action 对应的 Invoker 对象。

创建 ActionInvoker 的过程如下:

代码如下:

context.Handler = (c) =>
{var routeData = c.GetRouteData();var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);var invoker = _actionInvokerFactory.CreateInvoker(actionContext);return invoker.InvokeAsync();
};

开始调用 invoker.InvokerAsync()

InvokerAsync 中的过程其实就是一个管线过滤器执行的过程,在这个管线中,每一步 Action的流转都具有当前管线执行的一个状态,这个过程有点像中间件的执行过程,我们一起来看下。

在 MVC Core 中,过滤器分为 5 大类,分别是:

  • 授权过滤器(Authorization Filters)
  • 资源过滤器(Resource Filters)
  • Action过滤器(Action Filters)
  • 异常过滤器(Exception Filters)
  • Result 过滤器(Result Filters)

关于过滤器的管道执行主要包含两个类,分别是 ResourceInvokerControllerActionInvoker。 其中 ControllerActionInvoker 继承自ResourceInvoker 并且实现了 IActionInvoker 接口。

下面,我们就来看一下 源码 中关于这一部分是如何实现的。

ResourceInvoker

ResourceInvoker 中,拥有管线的入口函数,即 InvokeAsync(),在这个函数中又调用了 InvokeFilterPipelineAsync()

以下这个函数是调用过程的核心函数:

private async Task InvokeFilterPipelineAsync()
{var next = State.InvokeBegin;// scope 用来标明下一个调用的方法是哪个,// 以及初始化的状态是哪种。 // 最外面的“scope”是“Scope.Invoker”,不需要任何类型的“context”或“result”。var scope = Scope.Invoker;// 'state'用于状态之间的转换期间的内部状态处理。// 实际上,也就是说将过滤器实例存储在“state”中,然后在下一个状态下检索它。var state = (object)null;// 当到达终点 state 时, `isCompleted` 会被设置为truevar isCompleted = false;while (!isCompleted){await Next(ref next, ref scope, ref state, ref isCompleted);}
}

可以看到,里面有一个Next循环,直到 isCompleted 为 true 时才停止,那么在这个 Next 中会依次执行各个过滤器。

管线针对于MVC预设的几个过滤器他们对应的执行顺序如下图:

ResourceInvoker 中,主要处理两种类型的过滤器,他们是 Authorization FiltersResource Filter

  • Authorization Filters

在 Next 中,会把处理的每个阶段的状态作为一个枚举类型,然后使用的 Switch Case 手动进行的状态的处理,那么在处理 Authorization 相关的过滤器的时候,其 State 具有以下状态:

AuthorizationBegin,
AuthorizationNext,
AuthorizationAsyncBegin,
AuthorizationAsyncEnd,
AuthorizationSync,
AuthorizationShortCircuit,
AuthorizationEnd,

关于每一种状态的处理大家可以直接看这里的
代码 ,比较简单就不详细说了。

有一点需要说明的是 在 过滤器的处理中,AuthorizationShortCircuit 这个状态是用来处理短路的情况的,也就是说在过滤器中如果对 Context.Result 赋值了,那么会短路过滤器的管道,直接将 isCompleted 标记为 true,结束管道流程。

AuthorizationSync 这个状态是执行 OnAuthorization 的关键流程,IAuthorizationFilter 的实现将在这个流程中执行。

  • Resource Filters

Resource Filter 是 ASP.NET Core MVC 中新增加的一个过滤器,它在管道中的调用顺序仅次于Authorization,在其后执行。用户可以实现 IResourceFilterIAsyncExceptionFilter 接口来自定义 Resource 过滤器, 它主要是此过滤器来实现一些需要短路过滤器管线的一些操作。比如说:缓存(如果被命中,则直接返回,短路管线)。

下面是 Resource 中涉及的所有状态。

ResourceBegin,
ResourceNext,
ResourceAsyncBegin,
ResourceAsyncEnd,
ResourceSyncBegin,
ResourceSyncEnd,
ResourceShortCircuit,
ResourceInside,
ResourceOutside,
ResourceEnd

ControllerActionInvoker

ControllerActionInvoker 重写了 ResourceInvoker 中的InvokeInnerFilterAsync()方法,然后经由 ControllerActionInvokerProvider 实例化。

既然流程是从 ResourceInvoker 开始的,那么我们看一下在 ResourceInvoker 中哪一步调用了 InvokeInnerFilterAsync()

ResourceInvoker 中主要有两个状态来调用它,一个是 State.ResourceNext,另外一个是 State.ResourceInside

State.ResourceNext 中如果没有检测到有 Resource Filter 这直接开始下一阶段的 Filter调用,即 ControllerActionInvoker 中处理的过滤器。

State.ResourceInside 中会将下一阶段的状态设置为 State.ResourceOutside,然后开始启动调用 InvokeInnerFilterAsync(),当 ControllerActionInvoker 中的过滤器执行完成之后,再回过头来执行 ResourceOutside 相关内容。

好了,现在流程已经正式来到了 ControllerActionInvoker

ControllerActionInvoker 中,主要处理 3 种类型的过滤器。

  • Exception Filters

实现了 IExceptionFilter 接口或 IAsyncExceptionFilter 接口的过滤器,处理包括发生在 Controller 创建及 模型绑定 期间出现的异常。它们只在管道内发生异常时才会被调用。

  • Action Filters

实现了 IActionFilter 接口或 IAsyncActionFilter 接口的过滤器,它们可以在 action 方法执行的前后被执行。

  • Result Filters

实现了 IResultFilterIAsyncResultFilter 接口。Result Filter 在 Action Result 执行体的周围执行。当 Action 或 Action 过滤器产生 Action 结果时,只有成功运行的才会执行结果过滤器。如果异常过滤器处理了异常,那么结果过滤器就不会运行——除非异常过滤器将异常设置为null。

源码流程

下面是 ControllerActionInvoker 中的 InvokeInnerFilterAsync


protected override async Task InvokeInnerFilterAsync()
{var next = State.ResourceInsideBegin;var scope = Scope.Resource;var state = (object)null;var isCompleted = false;while (!isCompleted){await Next(ref next, ref scope, ref state, ref isCompleted);}
}

它的起始状态从 State.ResourceInsideBegin 开始,核心方法还是 Next 方法。

在源代码中,状态的流转是先从 Exception 开始,然后对Exception过滤器进行"压栈",但是并不会执行过滤器中的代码,接着会执行Action相关状态代码,在 State.ActionAsyncBegin 这个状态中会执行 Action Filters 中的 OnActionExecuting,然后在 State.ActionSyncEnd 这个状态中执行OnActionExecuted

注意Action Filter 的执行代码由一个 Try Catch 代码块包装,当发生异常的时候,MVC会把这些信息包装成为一个 ActionExecutedContext 对象,然后会接着执行 Action Filter 里面 OnActionExecuted

Action Filter 相关的过滤器执行完成之后会将状态置为 State.ExceptionSyncEnd 开始执行 Exception Filter 相关业务。

在 整个管道的流程中,用户在 Action 中的代码是在 State.ActionInside 这个状态中执行的,它在 Action FilterOnActionExecuting 后执行。

用户代码Action的调用主要是下面这个函数:

private async Task InvokeActionMethodAsync()
{var controllerContext = _controllerContext;var executor = _executor;var controller = _controller;var arguments = _arguments;//构建Action参数var orderedArguments = ControllerActionExecutor.PrepareArguments(arguments, executor);IActionResult result = null;var returnType = executor.MethodReturnType;// void 返回结果,执行后返回 EmptyResultif (returnType == typeof(void)){executor.Execute(controller, orderedArguments);result = new EmptyResult();}// Task 返回结果,执行后返回 EmptyResultelse if (returnType == typeof(Task)){await (Task)executor.Execute(controller, orderedArguments);result = new EmptyResult();}// IActionResult 返回结果,执行后返回 IActionResultelse if (executor.TaskGenericType == typeof(IActionResult)){result = await (Task<IActionResult>)executor.Execute(controller, orderedArguments);if (result == null){throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));}}//是否为 IActionResult 的子类型else if (executor.IsTypeAssignableFromIActionResult){//是否为异步Actionif (_executor.IsMethodAsync){result = (IActionResult)await _executor.ExecuteAsync(controller, orderedArguments);}else{result = (IActionResult)_executor.Execute(controller, orderedArguments);}if (result == null){throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType));}}//非异步方法else if (!executor.IsMethodAsync){var resultAsObject = executor.Execute(controller, orderedArguments);result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject){DeclaredType = returnType,};}else if (executor.TaskGenericType != null){var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject){DeclaredType = executor.TaskGenericType,};}else{throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(executor.MethodInfo.Name,executor.MethodInfo.DeclaringType));}_result = result;
}

下面是 ControllerActionInvoker 中用到的所有状态。

private enum State
{ResourceInsideBegin,ExceptionBegin,ExceptionNext,ExceptionAsyncBegin,ExceptionAsyncResume,ExceptionAsyncEnd,ExceptionSyncBegin,ExceptionSyncEnd,ExceptionInside,ExceptionHandled,ExceptionEnd,ActionBegin,ActionNext,ActionAsyncBegin,ActionAsyncEnd,ActionSyncBegin,ActionSyncEnd,ActionInside,ActionEnd,ResultBegin,ResultNext,ResultAsyncBegin,ResultAsyncEnd,ResultSyncBegin,ResultSyncEnd,ResultInside,ResultEnd,ResourceInsideEnd,
}

总结

本文详细描述了 MVC 在 Action 是如何激活的,以及在激活 Action 的过程中,过滤器管线中都做了哪些工作,并且讲解了其中的过程,以及各个过滤器的一些作用和功能。

如果你对 .NET Core 感兴趣可以关注我,我会定期在博客分享关于 .NET Core 的学习心得,如果你认为本篇文章对你有帮助的话,谢谢你的【推荐】。


本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-action.html
作者博客:Savorboard
欢迎转载,请在明显位置给出出处及链接

转载于:https://www.cnblogs.com/savorboard/p/aspnetcore-mvc-action.html

ASP.NET Core MVC 源码学习:详解 Action 的激活相关推荐

  1. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  2. ASP.NET Core MVC 源码学习:详解 Action 的匹配

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  3. ASP.NET Core MVC 源码学习:Routing 路由

    前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...

  4. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)(转)

    阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHandler.IHttpHandler.MvcHandler的说明 2.IHttpHandler解析 3.Mvc ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  6. ASP.NET Core 框架源码地址

    ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet  ...

  7. jsp漂亮的登录界面源码_【案例+源码】详解MVC框架模式及其应用

    案例+源码]详解MVC框架模式及其应用 写在开头: 首先我们需要知道,框架模式.模式.开发模式是三种不同的概念,但他们的目的都一样:解耦! 1.关于MVC框架模型 MVC是三个单词的缩写: M,Mod ...

  8. Android 源码编译详解【合集篇】

    Android 源码编译详解[一]:服务器硬件配置及机型推荐 做 Android系统开发多年,开发环境都是入职就搭建好了,入职时拿个账号密码就直接开始搞开发了,年初换了新公司,所有的项目都是刚起步,一 ...

  9. ASP.NET Core 中间件(Middleware)详解

    ASP.NET Core 中间件(Middleware)详解 原文:ASP.NET Core 中间件(Middleware)详解 本文为官方文档译文,官方文档现已非机器翻译 https://docs. ...

  10. Android四大组件之bindService源码实现详解

        Android四大组件之bindService源码实现详解 Android四大组件源码实现详解系列博客目录: Android应用进程创建流程大揭秘 Android四大组件之bindServic ...

最新文章

  1. ironpython 教程_「ironpython」VS2017 IronPython做界面
  2. Hibernate中的数据库方言(Dialect)
  3. 如何做好数字化体验管理,了解一下?
  4. javaweb学习总结(二十九)——EL表达式
  5. Win8节省C盘空间攻略
  6. SAP Spartacus维护CMS Component到Angular Component的源代码位置
  7. delete postman 传参_PostMan 传参boolean 类型,接口接受的值一直是false
  8. 2058. 找出临界点之间的最小和最大距离
  9. 文件拷贝(字符、字节)
  10. Java 方法使用总结(重载、数组输出、enum和switch、foreach和迭代器、可变长度参数、重载中使用可变长度参数)
  11. 云主机挂载硬盘 - 开机自动挂载
  12. springcloud2.0以上版本_eureka控制台显示_找不到${spring.cloud.client.ipAddress}_没有显示成IP地址---springcloud工作笔记165
  13. 热烈庆贺:一个月,由70名升级为60名!
  14. Java数据类型转换
  15. numberformat_解决NumberFormat的解析问题
  16. Win10系统隐藏磁盘
  17. 计算机网络课程设计家庭网,家庭无线局域网的组建
  18. 灰灰深入浅出讲解支持向量机(SVM)
  19. TDK是什么意思,TDK怎么写?怎么利于SEO优化?
  20. 下载了免费的epub电子书,如何用Windows电脑打开?

热门文章

  1. Router OS 全攻略
  2. include问题及错误解决
  3. mysql数据库调试_mysql数据库调试
  4. appscan无法连接到服务器_和平精英无法连接到服务器是怎么回事 最新解决方案...
  5. [编程题]手机屏幕解锁模式
  6. 1010 一元多项式求导 (25 分)—PAT (Basic Level) Practice (中文)
  7. L1-009 N个数求和 (20 分)—团体程序设计天梯赛
  8. python 粘包问题
  9. 51nod 1106 质数检测
  10. 【OOM】GC overhead limit exceeded