目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的选择
目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的选择
ASP.NET Web API能够根据请求激活目标HttpController的前提是能够根据请求选择出正确的HttpController,HttpController的选择在ASP.NET Web API中通过HttpControllerSelector来实现。[本文已经同步到《How ASP.NET Web API Works?》]
目录
HttpControllerSelector & DefaultHttpControllerSelector
获取目标HttpController的名称
解析HttpController名称与HttpControllerDescriptor映射
实例演示:如何选择有效的HttpController类型
根据请求选择HttpController
HttpControllerSelector & DefaultHttpControllerSelector
所有的HttpControllerSelector均实现了具有如下定义的接口IHttpControllerSelector。
1: public interface IHttpControllerSelector
2: {
3: IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
4: HttpControllerDescriptor SelectController(HttpRequestMessage request);
5: }
如下面的代码片断所示,该接口中定义了两个方法。GetControllerMapping方法返回一个描述所有HttpController类型的HttpControllerDescriptor对象与对应的HttpController名称之前的映射关系。针对请求对HttpController的选择实现在方法SelectController中,返回的是用以描述目标HttpController的HttpControllerDescriptor对象。
默认使用HttpControllerSelector依然注册到当前HttpConfiguration的ServicesContainer中,我们可以调用ServicesContainer具有如下定义的扩展方法GetHttpControllerSelector或者注册的HttpControllerSelector对象。
1: public static class ServicesExtensions
2: {
3: //其他成员
4: public static IHttpControllerSelector GetHttpControllerSelector(this ServicesContainer services);
5: }
如下的代码片断所示,HttpConfiguration默认使用的DefaultServices在初始化的过程中会根据指定的HttpConfiguration对象创建一个DefaultHttpControllerSelector对象,并将其注册为默认的HttpControllerSelector。
1: public class DefaultServices : ServicesContainer
2: {
3: //其他成员
4: public DefaultServices(HttpConfiguration configuration)
5: {
6: //其他操作
7: this.SetSingle<IHttpControllerSelector>(new DefaultHttpControllerSelector(configuration));
8: }
9: }
DefaultHttpControllerSelector类型定义在System.Web.Http.Dispatcher命名空间下。如下面的代码片断所示,DefaultHttpControllerSelector不仅仅实现了定义在IHttpControllerSelector接口中的两个方法,还定义另一个名为GetControllerName方法,它根据指定的请求消息得到对应的HttpController名称。
1: public class DefaultHttpControllerSelector : IHttpControllerSelector
2: {
3: public DefaultHttpControllerSelector(HttpConfiguration configuration);
4: public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
5: public virtual HttpControllerDescriptor SelectController(HttpRequestMessage request);
6: public virtual string GetControllerName(HttpRequestMessage request);
7: }
获取目标HttpController的名称
在“消息处理管道”中我们提到过:如果采用Web Host模式,消息管道的缔造者HttpControllerHandler在根据当前HTTP上下文创建用于表示请求的HttpRequestMessage后,会将ASP.NET路由系统解析当前请求得到的RouteData对象转换成HttpRouteData,并添加到HttpRequestMessage的属性字典中。对于Self Host模式来说,处于消息处理管道尾端的HttpRoutingDispatcher会利用ASP.NET Web API的路由系统对当前请求进行匹配并生成用以封装路由数据的HttpRouteData,这个HttpRouteData同样会被添加到表示当前请求的HttpRequestMessage中。
由于被附加到当前请求的HttpRouteData已经包含了目标HttpController的名称(对应的变量名为“controller”),所以我们可以从HttpRequestMessage中直接获取目标HttpController的名称。如下面的代码片断所示,DefaultHttpControllerSelector的GetControllerName方法也是按照这样的逻辑根据指定的HttpMessageMessage中提取目标HttpController的名称。
1: public class DefaultHttpControllerSelector : IHttpControllerSelector
2: {
3: //其他成员
4: public virtual string GetControllerName(HttpRequestMessage request)
5: {
6: IHttpRouteData routeData = request.GetRouteData();
7: if (routeData == null)
8: {
9: return null;
10: }
11: string str = null;
12: routeData.Values.TryGetValue<string>("controller", out str);
13: return str;
14: }
15: }
解析HttpController名称与HttpControllerDescriptor映射
DefaultHttpControllerSelector 的GetControllerMapping方法会返回类型为IDictionary<string, HttpControllerDescriptor>的字典,该字典对象包含了描述所有HttpController的HttpControllerDescriptor对象与对应HttpController名称之间的映射。
1: public class DefaultHttpControllerSelector : IHttpControllerSelector
2: {
3: //其他成员
4: private readonly HttpControllerTypeCache _controllerTypeCache;
5: public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
6: }
GetControllerMapping方法的实现逻辑简单。如上面的代码片断,DefaultHttpControllerSelector具有一个HttpControllerTypeCache类型的只读字段,通过它可以得到HttpController类型与名称之间的关系,GetControllerMapping方法只需要根据HttpController类型生成对应的HttpControllerDescriptor即可。
但是有个问题必须要考虑,由于同名的HttpController类型可能定义在不同的命名空间下,一个HttpController名称可能对应着多个HttpController类型,所以HttpControllerTypeCache缓存的数据是一个类型为Dictionary<string, ILookup<string, Type>>的字典对象的对象。但是这里的GetControllerMapping方法的返回类型为IDictionary<string, HttpControllerDescriptor>,那么同名HttpController类型应该如何取舍呢?
实际上同名的HttpController类型将不会体现在GetControllerMapping方法返回值中。举个简单的例子,我们通过如下的代码在命名空间HttpControllers1下定义了FooController和BarController,并在命名空间HttpControllers2下定义了BarController和BazController。
1: namespace HttpControllers1
2: {
3: public class FooController: ApiController{}
4: public class BarController : ApiController{}
5: }
6:
7: namespace HttpControllers2
8: {
9: public class BarController : ApiController{}
10: public class BazController : ApiController{}
11: }
假设有效的HttpController仅限于这4个,现在我们调用DefaultHttpControllerSelector的GetControllerMapping方法,返回的字典对象将只包含两个元素,对应的HttpController类型分别为FooController和BazController,BarController由于命名冲突将被丢弃。GetControllerMapping创建返回映射对象的逻辑基本上可以通过如下的代码片断来体现。
1: public class DefaultHttpControllerSelector : IHttpControllerSelector
2: {
3: //其他成员
4: private HttpControllerTypeCache _controllerTypeCache;
5: private HttpConfiguration _configuration;
6:
7: public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
8: {
9: Dictionary<string, HttpControllerDescriptor> mappings = new Dictionary<string, HttpControllerDescriptor>();
10: var keys = from item in _controllerTypeCache.Cache
11: where item.Value.Count() == 1
12: select item.Key;
13: foreach (string key in keys)
14: {
15: Type controllerType = _controllerTypeCache.Cache[key].First().First();
16: HttpControllerDescriptor descriptor = new HttpControllerDescriptor(_configuration, key, controllerType);
17: mappings.Add(key, descriptor);
18: }
19: return mappings;
20: }
21: }
需要强调一点的是:上面给出的代码并不是GetControllerMapping方法真正的实现。为了避免相同的操作在每次调用GetControllerMapping方法是被重复执行,DefaultHttpControllerSelector会对该方法返回的字典对象予以缓存,所以这样的操作只会执行一次。
实例演示:如何选择有效的HttpController类型
为了加深读者朋友们对此的认识,我们照例创建一个简单的演示实例。我们在一个空的ASP.NET MVC应用中创建了如下三个ApiController:FooController、BarController和BazController,简单起见,我们并没有为它们定义任何的成员。
1: namespace MvcApp.Controllers
2: {
3: public class FooController : ApiController{ }
4: public class BarController : ApiController{ }
5: public class BazController : ApiController{ }
6: }
我们创建一个具有如下定义的HomeController。在默认的Action方法Index中,我们利用GlobalConfiguration得到注册到当前ServicesContainer上的HttpControllerSelector。我们调用其GetControllerMapping方法返回一个包含描述所有HttpController的HttpControllerDescriptor对象与HttpController名称匹配关系的字典,该字典最终通过默认的View呈现出来。
1: public class HomeController : Controller
2: {
3: public ActionResult Index()
4: {
5: IHttpControllerSelector controllerSelector = GlobalConfiguration.Configuration.Services.GetHttpControllerSelector();
6: IDictionary<string, HttpControllerDescriptor> mappings = controllerSelector.GetControllerMapping();
7: return View(mappings);
8: }
9: }
如下所示的是Action方法Index对应View的定义,这是一个Model类型为IDictionary<string, HttpControllerDescriptor>的强类型View。在该View中,我们将所有HttpController的类型与它们匹配的名称以表格的形式呈现出来。
1: @using System.Web.Http.Controllers
2: @model IDictionary<string, HttpControllerDescriptor>
3: <html>
4: <head>
5: <title>HttpController映射</title>
6: </head>
7: <body>
8: <table>
9: <thead>
10: <tr>
11: <th>Controller Name</th>
12: <th>Controller Type</th>
13: </tr>
14: </thead>
15: <tbody>
16: @foreach (var item in Model)
17: {
18: <tr>
19: <td>@item.Key</td>
20: <td>@item.Value.ControllerType.Name</td>
21: </tr>
22: }
23: </tbody>
24: </table>
25: </body>
26: </html>
直接运行该程序后会在浏览器中呈现出如下图所示的输出结果,从中可以看出HttpControllerSelector确实能够将用于描述所有HttpController的HttpControllerDescriptor与它们对应的HttpController名称解析出来。

为了演示对HttpController名称冲突(即在不同的命名空间中定义同名的HttpController)的处理,我们删除FooController、BarController和BazController这三个类型,改换成如下的定义:命名空间HttpControllers1和HttpControllers2中均定义了一个名为BarController的ApiController。
1: namespace HttpControllers1
2: {
3: public class FooController: ApiController{}
4: public class BarController : ApiController{}
5: }
6:
7: namespace HttpControllers2
8: {
9: public class BarController : ApiController{}
10: public class BazController : ApiController{}
11: }
再次运行该程序后会在浏览器中呈现出如下图所示的输出结果。我们可以清楚地看出原本定义的4个有效的HttpController,只有FooController和BazController体现在GetControllerMapping方法的返回值中,命名冲突的两个同名的BarController均被排除在外。

根据请求选择HttpController
其实HttpControllerSelector的终极使命还是根据请求对目标HttpController的选择,这体现在它的SelectController方法上。对于DefaultHttpControllerSelector来说,其SelectController方法的实现逻辑非常简单:它只需要调用GetControllerName方法从给定的HttpRequestMessage提取目标HttpController的名称,然后根据此名称从GetControllerMapping方法的返回值中提取对应的HttpControllerDescriptor对象即可。
实现在SelectController方法中针对请求的HttpController选择机制虽然简单,但是针对几种特殊情况的处理我们也不应该忽视。首先,如果调用GetControllerName方法返回的HttpController名称为Null或者是一个空字符串,意味着ASP.NET路由系统(针对Web Host)或者ASP.NET Web API路由系统在对请求的解析过程中并没有得到表示目标HttpController的路由变量。在这种情况下,DefaultHttpControllerSelector会直接抛出一个响应状态为HttpStatusCode.NotFound的HttpResponseException异常,客户端自然就会接收到一个状态为“404, Not Found”的响应。其次,如果在调用GetControllerMapping方法返回的字典中并没有一个匹配的HttpControllerDescriptor,通过上面的分析我们知道如下两种情况会导致这样的问题:
- 在通过AssembliesResolver提供的程序集中并不曾定义这么一个有效的HttpController类型。
- 在通过AssembliesResolver提供的程序集中不同的命名空间下定义了多个同名的HttpController类型。
这两种情况下自然不能通过GetControllerMapping方法返回的字典对象来判断,但是却可以通过用于缓存HttpController类型的HttpControllerTypeCache对象来判断。对于第一种情况,依然会抛出一个响应状态为HttpStatusCode.NotFound的HttpResponseException异常。在第二种情况下,DefaultHttpControllerSelector会抛出一个InvalidOperationException异常,并提示“具有多个匹配的HttpController”。如果利用浏览器来访问的话就会得到如下图所示的输出结果。

出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的选择的更多相关文章
- 目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的创建
目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的创建 通过上面的介绍我们知道利用HttpControllerSelector可以根据 ...
- 剖析Asp.Net Web API中HttpController的激活
在Asp.Net Web API中,请求的目标是定义在某个HttpController中的某个Action方法.当请求经过Asp.Net Web API消息处理管道到达管道"龙尾" ...
- ASP.NET Web API中的Controller
虽然通过Visual Studio向导在ASP.NET Web API项目中创建的 Controller类型默认派生与抽象类型ApiController,但是ASP.NET Web API框架本身只要 ...
- 在 ASP.NET Web API 中使用 Attribute 统一处理异常
并非所有的异常都需要 try-catch 进行重复的处理,这会导致大量的重复性代码,一旦后续系统出现异常处理机制的修改,随着代码量增多,修改也会变的更加困难. ASP.NET Web API 中特别增 ...
- 在ASP.NET Web API中使用OData
http://www.alixixi.com/program/a/2015063094986.shtml 一.什么是ODataOData是一个开放的数据协议(Open Data Protocol)在A ...
- ASP.NET Web API 中的异常处理(转载)
转载地址:ASP.NET Web API 中的异常处理
- 【ASP.NET Web API教程】6.2 ASP.NET Web API中的JSON和XML序列化
谨以此文感谢关注此系列文章的园友!前段时间本以为此系列文章已没多少人关注,而不打算继续下去了.因为文章贴出来之后,看的人似乎不多,也很少有人对这些文章发表评论,而且几乎无人给予“推荐”.但前几天有人询 ...
- Asp.Net Web API 2第十三课——ASP.NET Web API中的JSON和XML序列化
前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html 本文描述ASP.NET W ...
- ASP.NET WEB API 中的路由调试与执行过程跟踪
路由调试 RouteDebugger 是调试 ASP.NET MVC 路由的一个好的工具,在ASP.NET WEB API中相应的有 WebApiRouteDebugger ,Nuget安装 Inst ...
随机推荐
- 【高德地图API】从零开始学高德JS API(七)——定位方式大揭秘
原文:[高德地图API]从零开始学高德JS API(七)——定位方式大揭秘 摘要:关于定位,分为GPS定位和网络定位2种.GPS定位,精度较高,可达到10米,但室内不可用,且超级费电.网络定位,分为w ...
- BIP Requests Are Failing With Error "OPP Error Oracle.apps.xdo.XDOException: Error Creating Lock Fil
In this Document Symptoms Cause _afrLoop=975833031487795&id=1512691.1&displayIndex=1&a ...
- HDU多赛学校9 HDU4969 Just a Joke 【数学积分】
数学题目 意甲冠军:的男孩向圆上的女孩跑去,保持男孩女孩圆心在同一条直线上.这过程中,男孩女孩均匀速 给出男孩女孩速度,圆的半径,男孩最长能跑的距离 问男孩是否能跑到女孩那里 能够用积分来解这道题,我 ...
- 在Magento产品页面的使用jqZoom
Magento在产品页面提供了一个简单的图片放大功能,不是非常好,假设考虑使用放大镜来使用户查看产品的大图.能够考虑使用基于jQuery的插件,jqZoom便是一款优秀的放大镜插件.以下将介绍如何把j ...
- 《python源代码剖析》笔记 python虚拟机中的函数机制
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在运行函数调用时会动态地创建新的 PyFrameObject对象, 这 ...
- linux网络编程学习笔记之四 -----多-threaded服务器
对于使用过程中并发.通过实现更轻量级线程. 每个线程都是一个独立的逻辑流. 主题是CPU在执行调度的最小独立单位,这个过程是资源分配单元.当然,这是在微内核操作系统说.总之,这是唯一的一个操作系统内核 ...
- 删除Python UserWarning[已解决]
在使用MySQLdb包后,导入测试时发现一个警告. /usr/lib/python2.6/site-packages/setuptools-0.8-py2.6.egg/pkg_resources.py ...
- PHP 12 :字符串的操作
原文:PHP 12 :字符串的操作 本章介绍字符串的操作.之所以要把字符串单独拿出来讲,是因为字符串在每种语言里都是非常重要的.并且也是大家关心的.我们从以下几个方面介绍字符串: 字符串的表现形式. ...
- HBuilder CSS 自定义代码块
=begin 本文档是CSS代码块的编辑文件.注意不要把其他语言的设置放到css里来. HBuilder可使用ruby脚本来编辑代码块和增强操作命令. 1.编辑代码块 如果要新增一个代码块,复制如下一 ...
- Shell的学习就从重装系统开始吧
小标题:与Fedora的爱恨情仇 干巴巴的shell学习实在枯燥,看来学习姿势还是要从实践入手 起因:传说中的不作死就不会死,昨晚偶遇一本PDF,讲glade编辑界面的,以下就被吸引了,跟着讲解搞出个 ...