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

简介:

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

前言

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

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

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

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

 

控制器执行过程

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

代码1-1

1
2
3
4
5
6
7
8
9
10
11
12
    privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken)
    {
         IHttpRouteDatarouteData=request.GetRouteData();
         HttpControllerDescriptordescriptor= this .ControllerSelector.SelectController(request);     
         IHttpControllercontroller=descriptor.CreateController(request);    
         HttpConfigurationconfiguration=request.GetConfiguration();
         HttpControllerContextcontrollerContext=newHttpControllerContext(descriptor.Configuration, routeData, request) {
             Controller=controller,
             ControllerDescriptor=descriptor
         };
         returncontroller.ExecuteAsync(controllerContext, cancellationToken);
}


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

HttpControllerContext控制器上下文

示例代码1-2

1
2
3
4
5
6
7
8
9
10
11
publicclassHttpControllerContext
    {
         publicHttpControllerContext();
         publicHttpControllerContext(HttpConfigurationconfiguration, IHttpRouteDatarouteData, HttpRequestMessagerequest);
  
         publicHttpConfigurationConfiguration {  get set ; }
         publicIHttpControllerController {  get set ; }
         publicHttpControllerDescriptorControllerDescriptor {  get set ; }
         publicHttpRequestMessageRequest {  get set ; }
         publicIHttpRouteDataRouteData {  get set ; }
}


结合代码1-2和代码1-1可以看到在HttpControllerContext类型中对HttpConfiguration类型的对象,它的重要性不用多说了,里面包含着很多配置信息以及存放基础设施的容器对象,然后就是路由数据对象IHttpRouteData类型,以及最后的Http请求对象HttpRequestMessage类型的对象,并且在代码1-1中对HttpControllerContext类型中的ControllerControllerDescriptor属性进行了赋值,Controller属性对应的就是当前被创建好的控制器,而ControllerDescriptor属性则是表示Controller属性对应控制器的描述类型,现在回头再看一下HttpControllerContext类型的对象就知道它里面包含的内容是有多重要了。

现在我们再回到代码1-1中,最后我们看到是由IHttpController类型的变量controller调用方法ExecuteAsync()方法由此进入控制器中,一般控制器都是继承自ApiController,我们就从ApiController类型来入手。

 

ApiController类型

示例代码1-3

1
2
3
4
    publicabstractclassApiController : IHttpController, IDisposable
    {
         publicvirtualTask<System.Net.Http.HttpResponseMessage>ExecuteAsync(HttpControllerContextcontrollerContext, CancellationTokencancellationToken);
}

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

代码1-4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
publicvirtualTask<HttpResponseMessage>ExecuteAsync(HttpControllerContextcontrollerContext, CancellationTokencancellationToken)
    {
         HttpControllerDescriptorcontrollerDescriptor=controllerContext.ControllerDescriptor;
         ServicesContainercontrollerServices=controllerDescriptor.Configuration.Services;
         HttpActionDescriptoractionDescriptor=controllerServices.GetActionSelector().SelectAction(controllerContext);
         HttpActionContextactionContext=newHttpActionContext(controllerContext, actionDescriptor);
         FilterGroupinggrouping=newFilterGrouping(actionDescriptor.GetFilterPipeline());
         IEnumerable<IActionFilter>actionFilters=grouping.ActionFilters;
         IEnumerable<IAuthorizationFilter>authorizationFilters=grouping.AuthorizationFilters;
         IEnumerable<IExceptionFilter>exceptionFilters=grouping.ExceptionFilters;
         returnInvokeActionWithExceptionFilters(InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, ()=>actionDescriptor.ActionBinding.ExecuteBindingAsync(actionContext, cancellationToken).Then<HttpResponseMessage>( delegate  {
             this ._modelState=actionContext.ModelState;
             returnInvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () =>controllerServices.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken))();
         }, newCancellationToken(),  false ))(), actionContext, cancellationToken, exceptionFilters);
}


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

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

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

 

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

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

 

ApiControllerActionSelector控制器行为选择器

示例代码1-5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    publicclassApiControllerActionSelector : IHttpActionSelector
    {
         //Fields
         privatereadonlyobject_cacheKey;
         privateActionSelectorCacheItem_fastCache;
         privateconststringActionRouteKey= "action" ;
         privateconststringControllerRouteKey= "controller" ;
  
         //Methods
         publicApiControllerActionSelector();
         publicvirtualILookup< string , HttpActionDescriptor>GetActionMapping(HttpControllerDescriptorcontrollerDescriptor);
         privateActionSelectorCacheItemGetInternalSelector(HttpControllerDescriptorcontrollerDescriptor);
         publicvirtualHttpActionDescriptorSelectAction(HttpControllerContextcontrollerContext);
  
         //Nested Types
         privateclassActionSelectorCacheItem
         {
         }
  
         privateclassLookupAdapter : 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

1
2
3
4
5
6
         privatereadonlyReflectedHttpActionDescriptor[] _actionDescriptors;
         privatereadonlyILookup< string , ReflectedHttpActionDescriptor>_actionNameMapping;
         privatereadonlyIDictionary<ReflectedHttpActionDescriptor,  string []>_actionParameterNames=newDictionary<ReflectedHttpActionDescriptor,  string []>();
         privatereadonlyHttpMethod[] _cacheListVerbKinds=newHttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };
         privatereadonlyReflectedHttpActionDescriptor[][] _cacheListVerbs;
         privatereadonlyHttpControllerDescriptor_controllerDescriptor;


 

1.1基础信息初始化-ReflectedHttpActionDescriptor[] _actionDescriptors

这个时候初始化工作并没有做完,这时候会把MethodInfo[]数组中的每个MethodInfo实例封装成ReflectedHttpActionDescriptor类型的对象,对于类型稍后再说。在封装成ReflectedHttpActionDescriptor类型的对象后,也会将每个实例存至一个ReflectedHttpActionDescriptor类型的数组中。

1.2 基础信息初始化-IDictionary<ReflectedHttpActionDescriptor, string[]>_actionParameterNames

1.1的工作做完之后呢,就会对每个ReflectedHttpActionDescriptor类型的对象进行分析,分析啥?分析方法的参数名称,并且已1n的方式存在IDictionary<ReflectedHttpActionDescriptor, string[]>类型的键值队中。这里存放的值就是一个方法描述对象作为key值,value值是这个方法的所有参数名称。

 

1.3 基础信息初始化-ILookup<string, ReflectedHttpActionDescriptor> _actionNameMapping

这里的工作是根据1.1工作的结果,利用_actionDescriptors变量来根据ActionName分组,而最后_actionNameMapping中的值也是集合类型,不过每一项中的值都是个1n的键值队值。因为控制器方法可能存在重载的情况。

 

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

wKioL1QF563g4XnkAAEtLuVKapw839.jpg

 

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(stringa)Get(string a,string b)两个方法时,请求中如果有ab两个参数的话,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,看到HttpActionDescriptorReflectedHttpActionDescriptor)类型变量被赋值,回想下上面的过程,感觉过了好久一样。

 

最后贴一下很粗略的示意图

2

wKiom1QF57biPx_uAAMGaQj_SgA368.jpg

 

我们上面所讲的也不过只是在整个过程中的第一步过程。







     本文转自jinyuan0829 51CTO博客,原文链接http://blog.51cto.com/jinyuan/1548086 :,如需转载请自行联系原作者



相关文章
|
3月前
|
存储 开发框架 NoSQL
ASP.NET WEB——项目中Cookie与Session的用法
ASP.NET WEB——项目中Cookie与Session的用法
39 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET WEB——项目创建与文件上传操作
ASP.NET WEB——项目创建与文件上传操作
48 0
|
14天前
|
开发框架 缓存 前端开发
利用Visual Basic构建高效的ASP.NET Web应用
【4月更文挑战第27天】本文探讨使用Visual Basic与ASP.NET创建高效Web应用的策略,包括了解两者基础、项目规划、MVC架构、数据访问与缓存、代码优化、异步编程、安全性、测试及部署维护。通过这些步骤,开发者能构建出快速、可靠且安全的Web应用,适应不断进步的技术环境。
|
1天前
|
XML 开发框架 .NET
C#/ASP.NET应用程序配置文件app.config/web.config的增、删、改操作
C#/ASP.NET应用程序配置文件app.config/web.config的增、删、改操作
|
2月前
mvc.net分页查询案例——控制器(HomeController.cs)
mvc.net分页查询案例——控制器(HomeController.cs)
8 0
|
3月前
|
SQL 开发框架 .NET
ASP.NET WEB+EntityFramework数据持久化——考核练习库——1、用户管理系统(考点:查询列表、增加、删除)
ASP.NET WEB+EntityFramework数据持久化——考核练习库——1、用户管理系统(考点:查询列表、增加、删除)
72 0
|
3月前
|
SQL 开发框架 前端开发
ASP.NET WEB项目中GridView与Repeater数据绑定控件的用法
ASP.NET WEB项目中GridView与Repeater数据绑定控件的用法
36 0
|
3月前
|
SQL 开发框架 .NET
ASP.NET Web——GridView完整增删改查示例(全篇幅包含sql脚本)大二结业考试必备技能
ASP.NET Web——GridView完整增删改查示例(全篇幅包含sql脚本)大二结业考试必备技能
37 0
|
5月前
|
Web App开发 开发框架 .NET
asp.net基于WEB层面的云LIS系统平台源码
结合当今各检验科管理及实验室规模的不同状况,充分吸收当今IT科技的最新成就,开发出以高度产品化、功能强大、极易实施操作、并不断升级换代为主要特点的LIS系统。彻底解决检验科的信息孤岛,全面实现全院信息互通互联、高度共享,并为检验科的规范化管理提供了有力工具。
41 0
|
5月前
|
JSON 开发框架 .NET
ASP.NET Core Web API设置响应输出的Json数据格式的两种方式
ASP.NET Core Web API设置响应输出的Json数据格式的两种方式