目标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”。如果利用浏览器来访问的话就会得到如下图所示的输出结果。

作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 

目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的选择的更多相关文章

  1. 目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的创建

    目标HttpController在ASP.NET Web API中是如何被激活的:目标HttpController的创建 通过上面的介绍我们知道利用HttpControllerSelector可以根据 ...

  2. 剖析Asp.Net Web API中HttpController的激活

    在Asp.Net Web API中,请求的目标是定义在某个HttpController中的某个Action方法.当请求经过Asp.Net Web API消息处理管道到达管道"龙尾" ...

  3. ASP.NET Web API中的Controller

    虽然通过Visual Studio向导在ASP.NET Web API项目中创建的 Controller类型默认派生与抽象类型ApiController,但是ASP.NET Web API框架本身只要 ...

  4. 在 ASP.NET Web API 中使用 Attribute 统一处理异常

    并非所有的异常都需要 try-catch 进行重复的处理,这会导致大量的重复性代码,一旦后续系统出现异常处理机制的修改,随着代码量增多,修改也会变的更加困难. ASP.NET Web API 中特别增 ...

  5. 在ASP.NET Web API中使用OData

    http://www.alixixi.com/program/a/2015063094986.shtml 一.什么是ODataOData是一个开放的数据协议(Open Data Protocol)在A ...

  6. ASP.NET Web API 中的异常处理(转载)

    转载地址:ASP.NET Web API 中的异常处理

  7. 【ASP.NET Web API教程】6.2 ASP.NET Web API中的JSON和XML序列化

    谨以此文感谢关注此系列文章的园友!前段时间本以为此系列文章已没多少人关注,而不打算继续下去了.因为文章贴出来之后,看的人似乎不多,也很少有人对这些文章发表评论,而且几乎无人给予“推荐”.但前几天有人询 ...

  8. 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 ...

  9. ASP.NET WEB API 中的路由调试与执行过程跟踪

    路由调试 RouteDebugger 是调试 ASP.NET MVC 路由的一个好的工具,在ASP.NET WEB API中相应的有 WebApiRouteDebugger ,Nuget安装 Inst ...

随机推荐

  1. js面向对象+一般方法的选项卡

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. angular.js的一点理解

    对angular.js的一点理解 2015-01-14 13:18 by MrGeorgeZhao, 317 阅读, 4 评论, 收藏, 编辑 最近一直在学习angular.js.不得不说和jquer ...

  3. PHP 4:从Login进一步看到的

    原文:PHP 4:从Login进一步看到的 我们已经在PHP 3:从Login界面谈PHP标记谈到了PHP标记,不过其页面代码有一句 require_once('bookmark_fns.php'); ...

  4. 机器学习学习-Types of learning

    Types of learning 基于个人理解.于我们在面对一个详细的问题时.可以依据要达到的目标选择合适的机器学习算法来得到想要的结果.比方,推断一封电子邮件是否是垃圾邮件,就要使用分类(clas ...

  5. 怎样以学习单片机为契机,逐步成为优秀的project师

    现状 不知道阅读本文的读者,在初学单片机时是否和我以前一样迷茫.看到各种新的术语,疑惑不解:不知道从何学起:照着书中的样例一步一步做都没有问题,可是自己试着做东西,遇到各种问题却不会解决,向别人提问, ...

  6. 你不知道的 页面编码,浏览器选择编码,get,post各种乱码由来

    原文:你不知道的 页面编码,浏览器选择编码,get,post各种乱码由来 asp.net页面编码和浏览器的选择编码 每个asp.net的朋友都知道,在新版本的visual studio,在没有任何设置 ...

  7. Linux解决:svn: Can&#39;t connect to host &#39;*.*.*.*&#39;: 因为连接的方没有正确回答或连接在以后的时间

    svn服务启动,在server在可使用命令将文件检查,但它不能检测其他计算机.已经提出: "svn: Can't connect to host '*.*.*.*': 因为连接方在一段时间后 ...

  8. 基于C# 语言的两个html解析器

    基于C# 语言的两个html解析器 1)Html Agility Pack http://nsoup.codeplex.com/ 代码段示例: HtmlDocument doc = new HtmlD ...

  9. css Cursor:url()自定义鼠标指针样式为图片

    css自定义鼠标指针样式为图片Cursor:url()的使用,今天在项目中,要用到自定义鼠标样式,格式: css:{cursor:url('绝对路径的图片(格式:cur,ico)'),-moz-zoo ...

  10. IOC容器在框架中的应用

    IOC容器在框架中的应用 前言 在上一篇我大致的介绍了这个系列所涉及到的知识点,在本篇我打算把IOC这一块单独提取出来讲,因为IOC容器在解除框架层与层之间的耦合有着不可磨灭的作用.当然在本系列前面的 ...