ASP.NET Web API 控制器执行过程(一)
ASP.NET Web API 控制器执行过程(一)
前言
前面两篇讲解了控制器的创建过程,只是从框架源码的角度去简单的了解,在控制器创建过后所执行的过程也是尤为重要的,本篇就来简单的说明一下控制器在创建过后将会做哪些工作。
ASP.NET Web API 控制器执行过程
- ASP.NET Web API 控制器执行过程(一)
- ASP.NET Web API 控制器执行过程(二)
控制器执行过程
我们知道控制器的生成过程都是在HttpControllerDispatcher类型中来操作的,那我们要想知道控制器在创建过后执行操作的入口点也必须在HttpControllerDispatcher类型中才能发现。来看如下示例代码:
代码1-1
- private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- IHttpRouteData routeData = request.GetRouteData();
- HttpControllerDescriptor descriptor = this.ControllerSelector.SelectController(request);
- IHttpController controller = descriptor.CreateController(request);
- HttpConfiguration configuration = request.GetConfiguration();
- HttpControllerContext controllerContext = new HttpControllerContext(descriptor.Configuration, routeData, request) {
- Controller = controller,
- ControllerDescriptor = descriptor
- };
- return controller.ExecuteAsync(controllerContext, cancellationToken);
- }
看过前面两篇的朋友看到这里的代码一定会很熟悉了,控制器的生成过程就包含在了其中,在代码1-1中我们会看到HttpControllerContext类型,从它的名称来看想必大家也都知道了它的作用,代表着进入控制器处理阶段的逻辑上的上下文对象,并且封装着一些很重要的信息。
HttpControllerContext控制器上下文
示例代码1-2
- public class HttpControllerContext
- {
- public HttpControllerContext();
- public HttpControllerContext(HttpConfiguration configuration, IHttpRouteData routeData, HttpRequestMessage request);
- public HttpConfiguration Configuration { get; set; }
- public IHttpController Controller { get; set; }
- public HttpControllerDescriptor ControllerDescriptor { get; set; }
- public HttpRequestMessage Request { get; set; }
- public IHttpRouteData RouteData { get; set; }
- }
结合代码1-2和代码1-1可以看到在HttpControllerContext类型中对HttpConfiguration类型的对象,它的重要性不用多说了,里面包含着很多配置信息以及存放基础设施的容器对象,然后就是路由数据对象IHttpRouteData类型,以及最后的Http请求对象HttpRequestMessage类型的对象,并且在代码1-1中对HttpControllerContext类型中的Controller和ControllerDescriptor属性进行了赋值,Controller属性对应的就是当前被创建好的控制器,而ControllerDescriptor属性则是表示Controller属性对应控制器的描述类型,现在回头再看一下HttpControllerContext类型的对象就知道它里面包含的内容是有多重要了。
现在我们再回到代码1-1中,最后我们看到是由IHttpController类型的变量controller调用方法ExecuteAsync()方法由此进入控制器中,一般控制器都是继承自ApiController,我们就从ApiController类型来入手。
ApiController类型
示例代码1-3
- public abstract class ApiController : IHttpController, IDisposable
- {
- public virtual Task<System.Net.Http.HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken);
- }
在代码1-3中我们可以看到再ApiController类型中定义了ExecuteAsync()方法,ApiController为抽象类型,控制器的主要执行过程也就是都在ExecuteAsync()方法中,下面我看一下具体的实现,如下示例代码。
代码1-4
- public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
- {
- HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;
- ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
- HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
- HttpActionContext actionContext = new HttpActionContext(controllerContext, actionDescriptor);
- FilterGrouping grouping = new FilterGrouping(actionDescriptor.GetFilterPipeline());
- IEnumerable<IActionFilter> actionFilters = grouping.ActionFilters;
- IEnumerable<IAuthorizationFilter> authorizationFilters = grouping.AuthorizationFilters;
- IEnumerable<IExceptionFilter> exceptionFilters = grouping.ExceptionFilters;
- return InvokeActionWithExceptionFilters(InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, () => actionDescriptor.ActionBinding.ExecuteBindingAsync(actionContext, cancellationToken).Then<HttpResponseMessage>(delegate {
- this._modelState = actionContext.ModelState;
- return InvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () => controllerServices.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken))();
- }, new CancellationToken(), false))(), actionContext, cancellationToken, exceptionFilters);
- }
代码1-4中定义了控制器的执行过程,我们就从源码的角度去了解一下控制器的执行过程。
在代码1-4中先是从控制器上下文对象中获取当前控制器类型的描述对象HttpControllerDescriptor类型的实例,而后从HttpControllerDescriptor类型实例从获取在HttpConfiguration中的服务容器ServicesContainer类型的实例,对于这些类型前面的篇幅或多或少的讲过了。
在这之后从服务容器中获取IHttpActionSelector类型的行为选择器并且经过筛选获取到最佳匹配的HttpActionDescriptor类型,在之前也有讲到过HttpControllerDescriptor,这里的HttpActionDescriptor跟其相似,就是表示控制其行为(方法)的元数据信息。
下面我就来讲解一下控制器行为选择器的执行过程,也就是它筛选方法的几个步骤。
首先我们要知道控制器行为选择器的类型,从代码1-4中可以看到是通过服务容器对象的扩展方法来获取的,在前面的篇幅也都讲过了,这里可以得知我们要查看的控制器行为选择器的类型就是ApiControllerActionSelector类型。
ApiControllerActionSelector控制器行为选择器
示例代码1-5
- public class ApiControllerActionSelector : IHttpActionSelector
- {
- // Fields
- private readonly object _cacheKey;
- private ActionSelectorCacheItem _fastCache;
- private const string ActionRouteKey = "action";
- private const string ControllerRouteKey = "controller";
- // Methods
- public ApiControllerActionSelector();
- public virtual ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);
- private ActionSelectorCacheItem GetInternalSelector(HttpControllerDescriptor controllerDescriptor);
- public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);
- // Nested Types
- private class ActionSelectorCacheItem
- {
- }
- private class LookupAdapter : ILookup<string, HttpActionDescriptor>, IEnumerable<IGrouping<string, HttpActionDescriptor>>, IEnumerable
- {
- }
- }
从代码1-5中我们可以看到ApiControllerActionSelector类型中包含着两个私有类,这两个私有类后面会有讲到起到的作用也很重要。
下面我们还是回到代码1-4中的逻辑,从调用控制器行为选择器中调用SelectAction()方法开始。
在ApiControllerActionSelector类型中调用SelectAction()时,实际是由SelectAction()方法调用GetInternalSelector()方法生成一个控制器方法的缓存对象,也就是ApiControllerActionSelector类型的私有类ActionSelectorCacheItem,而真正的筛选工作都是由它来执行的,所以下面才是介绍的重点。
控制器方法选择器-筛选方法的步骤
1初始化筛选
在ActionSelectorCacheItem类型的初始化的时候, ActionSelectorCacheItem实例中会首先根据HttpControllerDescriptor对象获取到控制器本身的类型,然后利用反射的技术根据条件获取到当前控制器类型中的所有方法,最后保存为MethodInfo[]。而所谓的条件就是(BindingFlags.Public 、BindingFlags.Instance、方法所属类型必须是ApiController类型的)。
我们看下ActionSelectorCacheItem类型中的字段信息,这些字段里存放的都是很重要的数据,后面会一一说明。
示例代码1-6
- private readonly ReflectedHttpActionDescriptor[] _actionDescriptors;
- private readonly ILookup<string, ReflectedHttpActionDescriptor> _actionNameMapping;
- private readonly IDictionary<ReflectedHttpActionDescriptor, string[]> _actionParameterNames = new Dictionary<ReflectedHttpActionDescriptor, string[]>();
- private readonly HttpMethod[] _cacheListVerbKinds = new HttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };
- private readonly ReflectedHttpActionDescriptor[][] _cacheListVerbs;
- private readonly HttpControllerDescriptor _controllerDescriptor;
1.1基础信息初始化- ReflectedHttpActionDescriptor[] _actionDescriptors
这个时候初始化工作并没有做完,这时候会把MethodInfo[]数组中的每个MethodInfo实例封装成ReflectedHttpActionDescriptor类型的对象,对于类型稍后再说。在封装成ReflectedHttpActionDescriptor类型的对象后,也会将每个实例存至一个ReflectedHttpActionDescriptor类型的数组中。
1.2 基础信息初始化- IDictionary<ReflectedHttpActionDescriptor, string[]> _actionParameterNames
在1.1的工作做完之后呢,就会对每个ReflectedHttpActionDescriptor类型的对象进行分析,分析啥?分析方法的参数名称,并且已1:n的方式存在IDictionary<ReflectedHttpActionDescriptor, string[]>类型的键值队中。这里存放的值就是一个方法描述对象作为key值,value值是这个方法的所有参数名称。
1.3 基础信息初始化- ILookup<string, ReflectedHttpActionDescriptor> _actionNameMapping
这里的工作是根据1.1工作的结果,利用_actionDescriptors变量来根据ActionName分组,而最后_actionNameMapping中的值也是集合类型,不过每一项中的值都是个1:n的键值队值。因为控制器方法可能存在重载的情况。
1.4 基础信息初始化-ReflectedHttpActionDescriptor[][] _cacheListVerbs
_cacheListVerbs值的初始化在最后,它的定义是一个二维数组,数组初始确定为三行N列,三行的控制是由_cacheListVerbKinds值控制的,这里初始化的是根据1.1工作的结果将_actionDescriptors值按Http方法类型进行分类,所以我说的是三行N列。
上面的这些步骤虽然有点烦,不过了解一下便于下面的理解。
2. Action名称筛选
示例代码1-7
- public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
在ActionSelectorCacheItem类型的SelectAction()方法中,将会进行剩下的几个筛选步骤,首先是会从方法的参数controllerContext中获取到路由数据对象,并且在其Values属性中查询是否有“Action”键对应的方法名称值,这个时候就会出现下面两种情况。
2.1如果注册的路由中有Action名称
这这种情况下会把上面1.3中的工作成果拿来,_actionNameMapping根据Action名称获取到一个ReflectedHttpActionDescriptor类型的数组,这个数组在整个流程中还不能往下走,还要经过筛选,筛选的规则是判断ReflectedHttpActionDescriptor中支持的Http方法类型是否支持当前请求的Http方法类型。
这里就涉及到在ReflectedHttpActionDescriptor类型的内部对IActionHttpMethodProvider类型特性的处理,不多说后面的文章会讲到。这里上一张图大家先留个印象。
图1
2.2没有路由名称的根据Http方法类型
在这种情况下,则是根据代码1-7中的方法的方法参数controllerContext中获取当前的请求类型,然后从1.4的工作结果中用_cacheListVerbs值根据当前请求的Http方法类型获取到ReflectedHttpActionDescriptor类型的数组实例。
3.根据请求参数名称、数量来匹配
3.1有参数的情况下
在这种情况下,会先把路由数据对象的Values属性中的Keys值存放在一个集合A中,然后再获取当前请求的查询字符串集合,并且把集合中的所有Key值添加到集合A中,这就使的所有请求的参数名称都在一个集合中,然后就会从1.2的结果中根据当前的ReflectedHttpActionDescriptor类型实例(这里是接着2的流程,所以这里是ReflectedHttpActionDescriptor类型的数组遍历执行)从_actionParameterNames获取对应的参数名称数组,然后是集合A会和获取的参数名称数组做一一的比较,看看集合A中是否包含参数名称,如果都有了则是满足条件。
这里返回的依然可能是ReflectedHttpActionDescriptor类型的数组,因为在一个方法有重载时,比如说Get(string a)和Get(string a,string b)两个方法时,请求中如果有a和b两个参数的话,Get(string a)也是满足条件的。
3.2无参数的情况下
这种情况下就比较简单了,从1.2的结果中还如上述那般,遍历的根据ReflectedHttpActionDescriptor类型实例获取参数名称数组,找到数组长度为0的。
4. 排除IActionMethodSelector类型特性的控制器方法
到最后一个筛选条件了,还是遍历ReflectedHttpActionDescriptor类型数组中的每一项,并且查找他们是否有使用实现了IActionMethodSelector接口的特性。
4.1有使用了实现IActionMethodSelector接口的特性
在这种情况下,会获取到IActionMethodSelector类型,并且调用其实现方法IsValidForRequest(),如果返回true的话这个ReflectedHttpActionDescriptor类型才可以被使用,这也是提供给我们自定义实现的一个便捷,通常情况下是下面的这种情况。
4.2没有使用实现IActionMethodSelector接口的特性
在这种情况下,会添加ReflectedHttpActionDescriptor类型到返回实例的集合中。
最后控制器行为选择器只会返回ReflectedHttpActionDescriptor类型集合的中的第一项且必须是只有一项,其他情况都会抛出异常。
这个时候思绪回到代码1-4,看到HttpActionDescriptor(ReflectedHttpActionDescriptor)类型变量被赋值,回想下上面的过程,感觉过了好久一样。
最后贴一下很粗略的示意图
图2
作者:金源
出处:http://www.cnblogs.com/jin-yuan/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面
ASP.NET Web API 控制器执行过程(一)的更多相关文章
- ASP.NET Web API 控制器执行过程
http://www.cnblogs.com/jin-yuan/p/3952605.html
- ASP.NET Web API 控制器创建过程(二)
ASP.NET Web API 控制器创建过程(二) 前言 本来这篇随笔应该是在上周就该写出来发布的,由于身体跟不上节奏感冒发烧有心无力,这种天气感冒发烧生不如死,也真正的体会到了什么叫病来如山倒,病 ...
- ASP.NET Web API 控制器创建过程(一)
ASP.NET Web API 控制器创建过程(一) 前言 在前面对管道.路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内 ...
- asp.net web api 控制器
1控制器操作的参数 控制器操作的参数可以是内置类型也可以是自定义类型,无参也是允许的. 2控制器操作返回值 类型 说明 void 操作返回值为void时,Web API返回空HTTP响应,其状态码为2 ...
- 如何让ASP.NET Web API的Action方法在希望的Culture下执行
在今天编辑推荐的<Hello Web API系列教程--Web API与国际化>一文中,作者通过自定义的HttpMessageHandler的方式根据请求的Accep-Language报头 ...
- ASP.NET Web API 安全筛选器
原文:https://msdn.microsoft.com/zh-cn/magazine/dn781361.aspx 身份验证和授权是应用程序安全的基础.身份验证通过验证提供的凭据来确定用户身份,而授 ...
- Web API 2 入门——使用ASP.NET Web API和Angular.js构建单页应用程序(SPA)(谷歌翻译)
在这篇文章中 概观 演习 概要 由网络营 下载网络营训练包 在传统的Web应用程序中,客户机(浏览器)通过请求页面启动与服务器的通信.然后,服务器处理请求,并将页面的HTML发送给客户端.在与页面的后 ...
- ABP文档 - Web Api 控制器
文档目录 本节内容: 简介 AbpApiController 基类 本地化 其它 过滤 审计日志 授权 防伪造过滤 工作单元 结果包装和异常处理 结果缓存 验证 模块绑定器 简介 通过Abp.Web. ...
- ABP理论学习之Web API控制器(新增)
返回总目录 本篇目录 介绍 AbpApiController基类 本地化 审计日志 授权 工作单元 其他 介绍 ABP通过Abp.Web.ApiNuget包集成了 ASP.NET Web API控制器 ...
随机推荐
- excel 日期/数字格式不生效需要但双击才会生效的解决办法
原因: Excel2007设置过单元格格式后,并不能立即生效必须挨个双击单元格,才能生效.数据行很多.效率太低. 原因:主要是一些从网上拷贝过来的日期或数字excel默认为文本格式或特殊-中文数字格式 ...
- 第一个shell脚本
打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好. #!/bin/bash echo "Hello World !" &quo ...
- 23种设计模式--工厂模式-Factory Pattern
一.工厂模式的介绍 工厂模式让我们相到的就是工厂,那么生活中的工厂是生产产品的,在代码中的工厂是生产实例的,在直白一点就是生产实例的类,代码中我们常用new关键字,那么这个new出来的实例 ...
- JavaScript动画-拖拽改变元素大小
▓▓▓▓▓▓ 大致介绍 拖拽改变元素大小是在模拟拖拽上增加了一些功能 效果:拖拽改变元素大小 ▓▓▓▓▓▓ 拖拽改变元素大小原理 首先这个方块得知道我们想要改变这个它的大小,所以我给它设定一个范围,当 ...
- thinkphp数据的查询和截取
public function NewsList(){ $this->assign('title','news'); $p = I('page',1); $listRows = 6; $News ...
- jQuery可自动播放动画焦点图插件Koala
Koala是一款简单而实用的jQuery焦点图幻灯片插件,焦点图不仅可以在播放图片的时候让图片有淡入淡出的动画效果,而且图片可以自动播放.该jQuery焦点图的每一张图片都可以设置文字描述,并浮动在图 ...
- Git使用详细教程(二)
分支 其实在项目clone下来后就有一个分支,叫做master分支.新建分支的步骤:右键项目→Git→Repository...→Branches... master分支应该是最稳定的,开发的时候,建 ...
- Selenium的PO模式(Page Object Model)[python版]
Page Object Model 简称POM 普通的测试用例代码: .... #测试用例 def test_login_mail(self): driver = self.driver driv ...
- Unity C#最佳实践(上)
本文为<effective c#>的读书笔记,此书类似于大名鼎鼎的<effective c++>,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针 ...
- SpringMVC(关于HandlerMapping执行流程原理分析)
请求过来先碰见中央调度器(前端调度器) //Determine handler for the current request; 对当前请求决定交给哪个handler, 当前请求地址过来 处理器执行链 ...