译自:http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection

 

本文描述了ASP.NET Web API怎么将一个HTTP请求路由到控制器的指定方法上。

对于更高级别路由概览,请看  Routing in ASP.NET Web API

本文看起来更像是对路由处理的详细过程。如果你想创建一个Web API项目并发现那些不想被路由的请求,那么希望本文可以帮助到你。

路由有三个主要的阶段:

  1. 将URI匹配到一个路由模板。
  2. 选择一个控制器
  3. 选择一个action(注:由于将action译为什么都觉得不好,干脆就不译,保持应为,其意义可以理解为方法,动作)

你还可以使用自定义实现来替换路由某部分处理过程。本文中,我将描述默认的行为。文末,我将指出哪些地方是可以扩展自定义行为的。

 

 

1.路由模板

一个路由模板看起来和URI路径很相似,区别是路由模板拥有大括号标记的占位符:

"api/{controller}/public/{category}/{id}"

当创建一个路由映射时,可以为部分或者全部占位符提供默认值:

defaults: new { category = "all" }

也可以设置约束,用来限制URI段中占位符的匹配方式:

constraints: new { id = @"\d+" }   // 只能匹配一个或多个数字。

路由框架会尝试匹配模板里面的URI路径。而纯文本字面量则必须全匹配。默认情况下,占位符将会匹配任何字符串,除非指定一个约束(一般是正则表达式)。路由框架并不会匹配URI的其他部分,比如主机名或者查询参数。路由框架将会在路由表中选择第一个匹配到路由。

有两个特殊的占位符:"{controller}" 和 "{action}"。

  • "{controller}"提供控制器的名字。
  • "{action}"提供action的名称。在Web API中,默认约定是忽略"action"(注:即可以不指定"{action}",而通过其他约定规则来分配action,将会在下面介绍到)。

1.1占位符默认值

如果为占位符设置了默认值,那么路由将会匹配丢失的这些URI片段,选择默认值进行来填充。例如:

routes.MapHttpRoute(

    name: "DefaultApi",

    routeTemplate: "api/{controller}/{category}",

    defaults: new { category = "all" }

);

URI"http://localhost/api/products"会匹配这个路由配置映射。而"{category}"片段被赋值为"all"。

1.2路由字典

如果路由框架找一个URI的匹配,那么将会创建一个包含每一个占位符的字段。键为占位符的名称,但不包含大括号。值为从URI路径中得到,或者使用默认值。路由字典存储在IHttpRouteData 对象中。

在路由匹配阶段,"{controller}" 和 "{action}"占位符,和其他占位符的处理方式一样,将会被简单地存储在字典中。

可以使用RouteParameter.Optional来作为默认值。如果一个占位符被赋值为可选的,那么将不会添加到路由字典中。例如:

routes.MapHttpRoute(

    name: "DefaultApi",

    routeTemplate: "api/{controller}/{category}/{id}",

    defaults: new { category = "all", id = RouteParameter.Optional }

);

对于URI路径"api/products",路由字典包含:

  • controller: "products"
  • category: "all"

对于"api/products/toys/123",则路由字典将包含:

  • controller: "products"
  • category: "toys"
  • id: "123"

默认值也可以设置为没有包含在路由模板中的值。如果产生匹配,那么此默认值将会存储在字典中。例如:

routes.MapHttpRoute(

    name: "Root",

    routeTemplate: "api/root/{id}",

    defaults: new { controller = "customers", id = RouteParameter.Optional }

);

如果URI路径是"api/root/8",那么字典将包含两个值:

  • controller: "customers"
  • id: "8"

 

 

2.控制器选择

使用IHttpControllerSelector.SelectController方法来处理控制器的选择。SelectController方法输入一个HttpRequestMessage 的实例,返回一个HttpControllerDescriptor。
DefaultHttpControllerSelector 类是IHttpControllerSelector的默认实现,其使用了一个简单的算法:

  1. 用键为"controller"来查询在路由字典中查找键为"controller"的值。
  2. 将获取到的值的末尾加上"Controller",得到类型名称。
  3. 根据类型名称,从Web API中查找此控制器。

例如:如果路由字典包含键值对"controller" = "products",那么控制器的类型为" ProductsController"。如果没有匹配的类型,或者有多个匹配,则路由框架会返回一个错误给客户端。

对于步骤3,DefaultHttpControllerSelector 使用IHttpControllerTypeResolver 接口来获取Web API控制器类型列表。

IHttpControllerTypeResolver 的默认实现是——返回所有:

(a)实现了IHttpController接口,

(b)不是抽象类,

(c)以"Controller"结尾

的公共类。

 

 

3.Action选择

控制器选择了之后,路由框架通过调用IHttpActionSelector.SelectAction方法选择action。SelectAction方法输入一个HttpControllerContext 并返回一个HttpActionDescriptor。

IHttpActionSelector的默认实现是ApiControllerActionSelector 类。以下执行 Action选择:

  • 请求的HTTP方法。
  • 如果标记了"{action}",获得路由模板中的"{action}"占位符。
  • 控制器的action参数。

在看action选择算法之前,我们需要进一步理解控制器的action.

控制器上的哪些方法被当做"actions"当action选择时,路由框架只关心控制器中的公有的实例方法。当然,要排除"特殊名称"的方法(如:构造函数,事件,运算符重载,等等),以及从ApiController类继承的方法。

HTTP 方法。路由框架只选择匹配请求的HTTP方法,判断如下:

  1. 通过用属性来标记为HTTP方法:AcceptVerbsHttpDeleteHttpGetHttpHead,HttpOptionsHttpPatchHttpPost, 或者 HttpPut
  2. 或者,如果方法的名称以"Get", "Post", "Put", "Delete", "Head", "Options", 或者 "Patch"开头的,那么默认约定支持HTTP方法。
  3. 如果上面两条都没有满足,那么此方法被认为是支持POST的。

参数绑定。参数绑定是Web API为参数创建一个值的过程。以下是参数绑定的默认规则:

  • 从URI获取(绑定)简单类型。
  • 从请求主体(request body)获取(绑定)复杂类型。

简单类型包括.NET框架的原生类型,再加上DateTime,Decimal,Guide,String和TimeSpan。对每一个action,最多可以从请求主体中读取一个参数。

可能需要重写默认的绑定规则。请看 WebAPI Parameter binding under the hood

有了以上的背景知识以后,以下是Action选择算法。

  1. 对于一个控制器,创建所有匹配HTTP请求方法的action列表。
  2. 如果路由字典中有"action"项,则删除action列表中那些名称不为此项的。
  3. 尝试从URI中匹配action参数,如下:
    1. 对于每一个action,从URI获取简单类型参数的列表,排除可选参数。
    2. 在此参数列表中,在路由字典或者URI请求字符串中,为每一个参数名找到一个匹配。匹配是不分大小写的,且是无序的。
    3. 选择参数列表中的每一个参数均匹配的action。
    4. 如果有多个匹配的action,则选择参数最多的一个。
  4. 忽略[NonAction]属性标记的actions。

步骤#3大概是最疑惑的。基本的想法是一个参数可以从URI、请求主体、自定义绑定,获取到值。对于从URI获取到的参数,我们要确保URI实际只包含一个参数对应的值,或者在路径(通过路由字典获得)中,或者在查询字符串中。

例如,考虑如下action:

public
void
Get(int id)

id参数
绑定到URI。因此,此action只能匹配到包含"id"值的URI,
或者在路由字典中有"id",或者查询字符串中有id。

可选参数是可以被忽略,因为他们是可选的。对于一个可选参数,如果从URI中获取不到值,也是允许的。

有很多原因表明,复杂类型是一个例外。复杂类型只能通过自定义绑定绑定到URI。但在这种情况下,路由框架预先不知道参数是否将绑定到特定的URI上。为了确认是否可以绑定,将会直接调用自定义绑定。选择算法的目标是在调用任何绑定之前,从静态的描述中选择出一个action。因此,复杂类型被排除在匹配算法之外。

在action选择之后,所有参数绑定被调用。

总结:

  • action必须匹配请求的HTTP方法。
  • 如果有"action"模板,action的名称必须匹配路由字典中的"action"项。
  • 对于action的每一个参数,如果参数是在URL中,则参数的名称必须在路由字典,或者查询字符串中被找到(排除可选参数和复杂类型的参数)
  • 尝试匹配最大量的参数。而最佳匹配可能是不带参数的方法。

 

 

4.扩展示例

路由映射:

routes.MapHttpRoute(

    name: "ApiRoot",

    routeTemplate: "api/root/{id}",

    defaults: new { controller = "products", id = RouteParameter.Optional }

);

routes.MapHttpRoute(

    name: "DefaultApi",

    routeTemplate: "api/{controller}/{id}",

    defaults: new { id = RouteParameter.Optional }

);

控制器:

public
class
ProductsController : ApiController

{

public
IEnumerable<Product> GetAll() { }

public Product GetById(int id, double version = 1.0) { }

[HttpGet]

public
void FindProductsByName(string name) { }

public
void Post(Product value) { }

public
void Put(int id, Product value) { }

}

HTTP请求:

GET http://localhost:34701/api/products/1?version=1.5&details=1

4.1路由匹配

此URI匹配名为"DefaultApi"的路由映射。路由字典包含两项:

  • controller: "products"
  • id: "1"

路由字典中不包含查询字符串参数——"version" and "details",但这两个参数在action选择时仍会被考虑。

4.2控制器选择

从路由字典中的"controller"项,控制器类型的ProductsController。

4.3Action选择

HTTP请求是一个GET请求。控制器支持GET的action是GetAll, GetById, andFindProductsByName. 路由字典中不包含"action"的项,所以我们并不需要匹配的action名称。

接下来,我们尝试为action匹配参数名称,只看GET action的。

Action 

要匹配的参数

GetAll

none 

GetById

"id" 

FindProductsByName

"name"

注意到GetById 的version参数不考虑的,因为它是可选参数。

GetAll 方法平凡匹配。GetById 方法也符合,因为路线字典有"身份证"。FindProductsByName 方法不匹配。

GetById 方法将被匹配,因为它匹配一个参数,与无参数GetAll相比。GetById 方法用以下参数值被调用:

  • id = 1
  • version = 1.5

 

请注意,尽管选择算法中不使用version参数,但是version的值来自URI查询字符串。

 

 

 

 

5.扩展点

Web API为路由处理的某些部分提供了扩展点。

接口

描述

IHttpControllerSelector

选择一个控制器。

IHttpControllerTypeResolver

获取控制器类型列表。
DefaultHttpControllerSelector从此列表中选择控制器的类型。

IAssembliesResolver

Gets the list of project assemblies. 获取项目中的程序集列表IHttpControllerTypeResolver接口使用此列表找到控制器类型。

IHttpControllerActivator

创建一个新的控制器实例

IHttpActionSelector

选择一个Action

IHttpActionInvoker

调用一个Action

 

使用HttpConfiguration 上的Services 集合,来提供这些接口的自定义实现。

var config = GlobalConfiguration.Configuration;

config.Services.Replace(typeof(IHttpControllerSelector), new
MyControllerSelector(config));

WebApi:路由和Action选择的更多相关文章

  1. C#进阶系列——WebApi 路由机制剖析:你准备好了吗?

    前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...

  2. 【ASP.NET Web API教程】4.2 路由与动作选择

    原文:[ASP.NET Web API教程]4.2 路由与动作选择 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本系列教程,请先看前面的内容. 4.2 Routing ...

  3. 【C#】 WebApi 路由机制剖析

    C#进阶系列——WebApi 路由机制剖析:你准备好了吗? 转自:https://blog.csdn.net/wulex/article/details/71601478 2017年05月11日 10 ...

  4. C#进阶系列——WebApi 路由机制剖析:你准备好了吗? 转载https://www.cnblogs.com/landeanfen/p/5501490.html

    阅读目录 一.MVC和WebApi路由机制比较 1.MVC里面的路由 2.WebApi里面的路由 二.WebApi路由基础 1.默认路由 2.自定义路由 3.路由原理 三.WebApi路由过程 1.根 ...

  5. WebApi 路由机制剖析

    阅读目录 一.MVC和WebApi路由机制比较 1.MVC里面的路由 2.WebApi里面的路由 二.WebApi路由基础 1.默认路由 2.自定义路由 3.路由原理 三.WebApi路由过程 1.根 ...

  6. WebApi路由机制详解

    随着前后端分离的大热,WebApi在项目中的作用也是越来越重要,由于公司的原因我之前一直没有机会参与前后端分离的项目,但WebApi还是要学的呀,因为这东西确实很有用,可单独部署.与前端和App交互都 ...

  7. Asp.Net Webapi路由基本设置

    1.直接在Global.asax中添加配置 如: using MvcApplication4.App_Start; using System; using System.Collections.Gen ...

  8. mvc webapi路由重写

    修改app_start/webapiconfig.cs using System.Web.Http; using System.Web.Routing; using Ninject; using Tx ...

  9. MVC和WebApi路由机制比较

    1.MVC使用的路由 在MVC中,默认路由机制是通过解析url路径来匹配Action.比如:/User/GetList,这个url就表示匹配User控制器下的GetList方法,这是MVC路由的默认解 ...

随机推荐

  1. 分模块的maven项目调试时报Source not found的解决办法

    一.背景 通常在开发中,我们经常会拆分我们的项目为一个个maven子工程,然后用一个父项目进行集成,并且子项目还会继承自父项目.当我们对这些项目进行debug调试的时候往往会在eclipse中出现so ...

  2. swift选择类或结构体

    按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体: 结构体的主要目的是用来封装少量相关简单数据值. 有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用. ? 任何在结 ...

  3. linux下U盘文件只读的解决办法

    . 在终端运行如下命令 tail -f /var/log/syslog . 插入有只读文件系统故障的U盘 . 观察命令行输出 输出局部如下: Jul :: cslouis-pc kernel: [15 ...

  4. Linux 中文乱码问题解决

    本文转载自:http://linux-wiki.cn/wiki/zh-hans/Java%E7%A8%8B%E5%BA%8F%E4%B8%AD%E6%96%87%E5%AD%97%E4%BD%93%E ...

  5. Linux下多窗口分屏式终端--Terminator

    很不错的分屏插件终端:https://pkgs.org/centos-6/repoforge-i386/terminator-0.95-3.el6.rf.noarch.rpm.html

  6. HTML学习之Web存储(五)

    本地数据库功能大大增强了Web应用对于本地存储数据的方式和功能.Web时代真正进入了:“客户端为重,服务端为轻的时代”. <!DOCTYPE html> <html xmlns=&q ...

  7. AOJ789 买酒

    买酒 Time Limit: 1000 ms   Case Time Limit: 1000 ms   Memory Limit: 64 MBTotal Submission: 70   Submis ...

  8. Mysql常用命令详解

    Mysql安装目录 数据库目录 /var/lib/mysql/ 配置文件 /usr/share/mysql(mysql.server命令及配置文件) 相关命令 /usr/bin(mysqladmin ...

  9. RTCP资料详解

    转自:http://www.360doc.com/content/13/0606/10/1317564_290865866.shtml RTCP RTCP协议将控制包周期发送给所有连接者,应用与数据包 ...

  10. linux 读写锁应用实例

    转自:http://blog.csdn.net/dsg333/article/details/22113489 /*使用读写锁实现四个线程读写一段程序的实例,共创建了四个新的线程,其中两个线程用来读取 ...