目标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 ...
随机推荐
- HDU 2516 取石子游戏 (博弈论)
取石子游戏 Problem Description 1堆石子有n个,两人轮流取.先取者第1次能够取随意多个,但不能所有取完.以后每次取的石子数不能超过上次取子数的2倍.取完者胜.先取者负输出" ...
- Openstack & Hadoop结合项目Sahara
Openstack 项目Sahara,主要是用来搭建Hadoop集群,利用虚拟出来的计算资源,高速搭建Hadoop集群. Sahara项目与OPenstack其它项目的关系: 图片转自:http:// ...
- 浅谈 js 数字格式类型
原文:浅谈 js 数字格式类型 很多人也许只知道 ,123.456,0xff 之类的数字格式.其实 js 格式还有很多数字格式类型,比如 1., .1 这样的,也有 .1e2 这样的. 可能有人说这是 ...
- Struts2_2_第一Struts2应用
web.xml文件的配置与1同样. 1)HelloWorld类的代码: public class HelloWorldAction { private String message; public S ...
- ORACLE 中极易混淆的几个 NAME 的分析和总结
我们知道,Oracle中的各种NAME会在我们的各个配置文件里常常出现,大致有下面这些: 在init.ora中有DB_NAME,INSTANCE_NAME,SERVICE_NAME 配置DG的时候,为 ...
- 安卓MonkeyRunner源码分析之启动
在工作中因为要追求完成目标的效率,所以更多是强调实战,注重招式,关注怎么去用各种框架来实现目的.但是如果一味只是注重招式,缺少对原理这个内功的了解,相信自己很难对各种框架有更深入的理解. 从几个月前开 ...
- 在vi中使用perltidy格式化perl代码
格式优美的perl代码不但让人赏心悦目,并且能够方便阅读. perltidy的是sourceforge的一个小项目,在我们写完乱七八糟的代码后,他能像变魔术一样把代码整理得漂美丽亮,快来体验一下吧!! ...
- 过度补脑系列:Nokia的不归路
没有数据,没有调查,只是突然想起诺基亚... 23号的一节酱油课,脑海中突然闪过两个关键字--nokia & android 于是用手机百度了一下,竟然真的出了新机型,NokiaX,X东24日 ...
- LINUX下FD_SET介绍
刚刚了解了linux下select系统调用,函数原型是 #include <sys/select.h> #include <sys/time.h> int select(int ...
- leetcode第五题--Longest Palindromic Substring
Problem:Given a string S, find the longest palindromic substring in S. You may assume that the maxim ...