ASP.NET Web API 控制器执行过程()

前言

前面两篇讲解了控制器的创建过程,只是从框架源码的角度去简单的了解,在控制器创建过后所执行的过程也是尤为重要的,本篇就来简单的说明一下控制器在创建过后将会做哪些工作。

ASP.NET Web API 控制器执行过程

  • ASP.NET Web API 控制器执行过程(一)
  • ASP.NET Web API 控制器执行过程(二)

控制器执行过程

我们知道控制器的生成过程都是在HttpControllerDispatcher类型中来操作的,那我们要想知道控制器在创建过后执行操作的入口点也必须在HttpControllerDispatcher类型中才能发现。来看如下示例代码:

代码1-1

  1. private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken)
  2. {
  3. IHttpRouteData routeData = request.GetRouteData();
  4. HttpControllerDescriptor descriptor = this.ControllerSelector.SelectController(request);
  5. IHttpController controller = descriptor.CreateController(request);
  6. HttpConfiguration configuration = request.GetConfiguration();
  7. HttpControllerContext controllerContext = new HttpControllerContext(descriptor.Configuration, routeData, request) {
  8. Controller = controller,
  9. ControllerDescriptor = descriptor
  10. };
  11. return controller.ExecuteAsync(controllerContext, cancellationToken);
  12. }

看过前面两篇的朋友看到这里的代码一定会很熟悉了,控制器的生成过程就包含在了其中,在代码1-1中我们会看到HttpControllerContext类型,从它的名称来看想必大家也都知道了它的作用,代表着进入控制器处理阶段的逻辑上的上下文对象,并且封装着一些很重要的信息。

HttpControllerContext控制器上下文

示例代码1-2

  1. public class HttpControllerContext
  2. {
  3. public HttpControllerContext();
  4. public HttpControllerContext(HttpConfiguration configuration, IHttpRouteData routeData, HttpRequestMessage request);
  5.  
  6. public HttpConfiguration Configuration { get; set; }
  7. public IHttpController Controller { get; set; }
  8. public HttpControllerDescriptor ControllerDescriptor { get; set; }
  9. public HttpRequestMessage Request { get; set; }
  10. public IHttpRouteData RouteData { get; set; }
  11. }

结合代码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

  1. public abstract class ApiController : IHttpController, IDisposable
  2. {
  3. public virtual Task<System.Net.Http.HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken);
  4. }

在代码1-3中我们可以看到再ApiController类型中定义了ExecuteAsync()方法,ApiController为抽象类型,控制器的主要执行过程也就是都在ExecuteAsync()方法中,下面我看一下具体的实现,如下示例代码。

代码1-4

  1. public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
  2. {
  3. HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;
  4. ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;
  5. HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
  6. HttpActionContext actionContext = new HttpActionContext(controllerContext, actionDescriptor);
  7. FilterGrouping grouping = new FilterGrouping(actionDescriptor.GetFilterPipeline());
  8. IEnumerable<IActionFilter> actionFilters = grouping.ActionFilters;
  9. IEnumerable<IAuthorizationFilter> authorizationFilters = grouping.AuthorizationFilters;
  10. IEnumerable<IExceptionFilter> exceptionFilters = grouping.ExceptionFilters;
  11. return InvokeActionWithExceptionFilters(InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, () => actionDescriptor.ActionBinding.ExecuteBindingAsync(actionContext, cancellationToken).Then<HttpResponseMessage>(delegate {
  12. this._modelState = actionContext.ModelState;
  13. return InvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () => controllerServices.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken))();
  14. }, new CancellationToken(), false))(), actionContext, cancellationToken, exceptionFilters);
  15. }

代码1-4中定义了控制器的执行过程,我们就从源码的角度去了解一下控制器的执行过程。

在代码1-4中先是从控制器上下文对象中获取当前控制器类型的描述对象HttpControllerDescriptor类型的实例,而后从HttpControllerDescriptor类型实例从获取在HttpConfiguration中的服务容器ServicesContainer类型的实例,对于这些类型前面的篇幅或多或少的讲过了。

在这之后从服务容器中获取IHttpActionSelector类型的行为选择器并且经过筛选获取到最佳匹配的HttpActionDescriptor类型,在之前也有讲到过HttpControllerDescriptor,这里的HttpActionDescriptor跟其相似,就是表示控制其行为(方法)的元数据信息。

下面我就来讲解一下控制器行为选择器的执行过程,也就是它筛选方法的几个步骤。

首先我们要知道控制器行为选择器的类型,从代码1-4中可以看到是通过服务容器对象的扩展方法来获取的,在前面的篇幅也都讲过了,这里可以得知我们要查看的控制器行为选择器的类型就是ApiControllerActionSelector类型。

ApiControllerActionSelector控制器行为选择器

示例代码1-5

  1. public class ApiControllerActionSelector : IHttpActionSelector
  2. {
  3. // Fields
  4. private readonly object _cacheKey;
  5. private ActionSelectorCacheItem _fastCache;
  6. private const string ActionRouteKey = "action";
  7. private const string ControllerRouteKey = "controller";
  8.  
  9. // Methods
  10. public ApiControllerActionSelector();
  11. public virtual ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);
  12. private ActionSelectorCacheItem GetInternalSelector(HttpControllerDescriptor controllerDescriptor);
  13. public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);
  14.  
  15. // Nested Types
  16. private class ActionSelectorCacheItem
  17. {
  18. }
  19.  
  20. private class LookupAdapter : ILookup<string, HttpActionDescriptor>, IEnumerable<IGrouping<string, HttpActionDescriptor>>, IEnumerable
  21. {
  22. }
  23. }

从代码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

  1. private readonly ReflectedHttpActionDescriptor[] _actionDescriptors;
  2. private readonly ILookup<string, ReflectedHttpActionDescriptor> _actionNameMapping;
  3. private readonly IDictionary<ReflectedHttpActionDescriptor, string[]> _actionParameterNames = new Dictionary<ReflectedHttpActionDescriptor, string[]>();
  4. private readonly HttpMethod[] _cacheListVerbKinds = new HttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };
  5. private readonly ReflectedHttpActionDescriptor[][] _cacheListVerbs;
  6. 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

  1. 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 控制器执行过程(一)的更多相关文章

  1. ASP.NET Web API 控制器执行过程

    http://www.cnblogs.com/jin-yuan/p/3952605.html

  2. ASP.NET Web API 控制器创建过程(二)

    ASP.NET Web API 控制器创建过程(二) 前言 本来这篇随笔应该是在上周就该写出来发布的,由于身体跟不上节奏感冒发烧有心无力,这种天气感冒发烧生不如死,也真正的体会到了什么叫病来如山倒,病 ...

  3. ASP.NET Web API 控制器创建过程(一)

    ASP.NET Web API 控制器创建过程(一) 前言 在前面对管道.路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内 ...

  4. asp.net web api 控制器

    1控制器操作的参数 控制器操作的参数可以是内置类型也可以是自定义类型,无参也是允许的. 2控制器操作返回值 类型 说明 void 操作返回值为void时,Web API返回空HTTP响应,其状态码为2 ...

  5. 如何让ASP.NET Web API的Action方法在希望的Culture下执行

    在今天编辑推荐的<Hello Web API系列教程--Web API与国际化>一文中,作者通过自定义的HttpMessageHandler的方式根据请求的Accep-Language报头 ...

  6. ASP.NET Web API 安全筛选器

    原文:https://msdn.microsoft.com/zh-cn/magazine/dn781361.aspx 身份验证和授权是应用程序安全的基础.身份验证通过验证提供的凭据来确定用户身份,而授 ...

  7. Web API 2 入门——使用ASP.NET Web API和Angular.js构建单页应用程序(SPA)(谷歌翻译)

    在这篇文章中 概观 演习 概要 由网络营 下载网络营训练包 在传统的Web应用程序中,客户机(浏览器)通过请求页面启动与服务器的通信.然后,服务器处理请求,并将页面的HTML发送给客户端.在与页面的后 ...

  8. ABP文档 - Web Api 控制器

    文档目录 本节内容: 简介 AbpApiController 基类 本地化 其它 过滤 审计日志 授权 防伪造过滤 工作单元 结果包装和异常处理 结果缓存 验证 模块绑定器 简介 通过Abp.Web. ...

  9. ABP理论学习之Web API控制器(新增)

    返回总目录 本篇目录 介绍 AbpApiController基类 本地化 审计日志 授权 工作单元 其他 介绍 ABP通过Abp.Web.ApiNuget包集成了 ASP.NET Web API控制器 ...

随机推荐

  1. excel 日期/数字格式不生效需要但双击才会生效的解决办法

    原因: Excel2007设置过单元格格式后,并不能立即生效必须挨个双击单元格,才能生效.数据行很多.效率太低. 原因:主要是一些从网上拷贝过来的日期或数字excel默认为文本格式或特殊-中文数字格式 ...

  2. 第一个shell脚本

    打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好. #!/bin/bash echo "Hello World !" &quo ...

  3. 23种设计模式--工厂模式-Factory Pattern

    一.工厂模式的介绍       工厂模式让我们相到的就是工厂,那么生活中的工厂是生产产品的,在代码中的工厂是生产实例的,在直白一点就是生产实例的类,代码中我们常用new关键字,那么这个new出来的实例 ...

  4. JavaScript动画-拖拽改变元素大小

    ▓▓▓▓▓▓ 大致介绍 拖拽改变元素大小是在模拟拖拽上增加了一些功能 效果:拖拽改变元素大小 ▓▓▓▓▓▓ 拖拽改变元素大小原理 首先这个方块得知道我们想要改变这个它的大小,所以我给它设定一个范围,当 ...

  5. thinkphp数据的查询和截取

    public function NewsList(){ $this->assign('title','news'); $p = I('page',1); $listRows = 6; $News ...

  6. jQuery可自动播放动画焦点图插件Koala

    Koala是一款简单而实用的jQuery焦点图幻灯片插件,焦点图不仅可以在播放图片的时候让图片有淡入淡出的动画效果,而且图片可以自动播放.该jQuery焦点图的每一张图片都可以设置文字描述,并浮动在图 ...

  7. Git使用详细教程(二)

    分支 其实在项目clone下来后就有一个分支,叫做master分支.新建分支的步骤:右键项目→Git→Repository...→Branches... master分支应该是最稳定的,开发的时候,建 ...

  8. Selenium的PO模式(Page Object Model)[python版]

     Page Object Model 简称POM  普通的测试用例代码: .... #测试用例 def test_login_mail(self): driver = self.driver driv ...

  9. Unity C#最佳实践(上)

    本文为<effective c#>的读书笔记,此书类似于大名鼎鼎的<effective c++>,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针 ...

  10. SpringMVC(关于HandlerMapping执行流程原理分析)

    请求过来先碰见中央调度器(前端调度器) //Determine handler for the current request; 对当前请求决定交给哪个handler, 当前请求地址过来 处理器执行链 ...