zoukankan      html  css  js  c++  java
  • 目标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名称解析出来。

    image

    为了演示对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均被排除在外

    image

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

    image

    作者:Artech
    出处:http://artech.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
     
  • 相关阅读:
    js的基本数据类型有哪些?
    页面优化的方式有哪些?
    为什么要用rem
    sass开发过程中遇到的几个坑
    js事件监听
    js数组去重的方法
    什么是怪异盒模型
    Cookie SameSite属性介绍及其在ASP.NET项目中的应用
    HttpServlet在第一个Servlet程序中的知识点
    Myeclipse 2017 创建第一个servlet步骤
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3274179.html
Copyright © 2011-2022 走看看