ASP.NET Web API 是怎么选择路由和操作的
2017-05-26 00:00
417 查看
摘要: 这是一篇翻译文
原文地址:https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
原文名称:Routing and Action Selection in ASP.NET Web API
原文作者:Mike Wasson
这篇文章描述了ASP.NET Web API 怎么将一个HTTP请求转换成指定控制器的指定操作。
Note:有关路由的高层概述,查看Routing in ASP.NET Web API.
本文关注的是路由过程的细节。如果你创建了一个Web API 项目并且发现一些请求不能像你预期的那样被路由,希望本文可以提供一些帮助。
路由有三个主要的部分:
将URI和路由模板互相匹配
选择控制器
选择操作
你可以将其中的任意一个过程替换为自定义行为。本文主要描述默认行为。在文末,我标记了你可以自定义的地方。
##路由模板
一个路由模板看起来很像URI路径,但是它有被大括号包裹的占位符:
当你创建一个路由的时候,可以给占位符提供默认值:
你也可以提供约束,来限制URI怎么匹配占位符
框架尝试将URI和模板匹配,模板中的字面量必需一字不差,占位符可以是任何值(除非指定了约束)。框架不会匹配URI的所有内容,比如主机名、查询参数。框架会选择路由表中匹配的第一个路由。
这里有两个特殊的占位符:"{controller}" 和 "{action}"
"{controller}" 提供了控制器的名字。
"{action}" 提供了操作的名字。在Web API里,惯例会省略"{action}"。
###默认值
如果提供了默认值,那些缺省了相应部分的URI也可以匹配成功。例如:
"
###路由字典
如果框架找到了与URI匹配的路由,它会创建一个字典,其中包含了每个占位符的值。键是占位符名称,不包括大括号。值取自URI路径或者默认值。这个字典被存储在IHttpRouteData对象里。
路由匹配的过程中,"{controller}"和"{action}"这两个特殊的占位符与其他占位符的处理方式一样,以键值的形式存储在字典中。
默认值可以设置为RouteParameter.Optional。如果一个占位符被分配到了这个值,它的值就不会被添加到路由字典里。例如:
对于"api/products"来说,路由字典将会包含:
controller: "products"
category: "all"
对于"api/products/toys/123"来说,路由字典将会包含:
controller: "products"
category: "toys"
id: "123"
即使路由模板里没有出现某个占位符,依然可以为其设置默认值。如果路由匹配成功,它的值就会被存储到字典里。例如:
如果URI路径是"api/root/8",路由字典将会包含:
controller: "customers"
id: "8"
##选择控制器
控制器的选择,由IHttpControllerSelector.SelectController方法掌管。这个方法获取HttpRequestMessage实例作为参数,返回一个HttpControllerDescriptor对象。其默认实现由DefaultHttpControllerSelector类提供。这个类使用了一个简单的算法:
在路由字典里查找键"controller"
获取此项的值,并追加字符串"Controller"以获得控制器类型名称
查找具有此类型名称的Web API控制器
举个例子,如果路由字典包含键值对 "controller" = "products",然后控制器类型就是"ProductsController"。如果没有匹配的类型或者匹配到了多个,框架会给客户端返回一个错误。
第三步,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口获取所有的Web API控制器类型。IHttpControllerTypeResolver的默认实现,返回(a)实现了IHttpController、(b)不是抽象的、(c)名称以"Controller"结尾的所有公共类。
##操作选择
选择了控制器之后,框架就会调用IHttpActionSelector.SelectAction方法来选择操作。这个方法接收HttpControllerContext对象并返回HttpActionDescriptor对象。
其默认实现由ApiControllerActionSelector类提供。
若要选择一个操作,它将查看以下内容:
请求的Http方法
路由模板中的"{action}"占位符(如果存在的话)
控制器上所有操作的参数
在查看选择算法之前,我们需要了解一些关于控制器操作的事情。
控制器上的哪个方法会被当做“操作”?[b]当选择一个操作时,框架只看控制器上的公共方法。此外,还排除了一些具有“特殊名称”的方法(构造函数、事件、运算符重载等)和从ApiController[/b]类继承的方法。
Http 方法。框架只选择与请求的Http方法匹配的操作,如下所示:
可以使用属性指定Http方法:AcceptVerbs,HttpDelete,HttpGet,HttpHead,HttpPatch,HttpPost,或者HttpPut。
另外,如果控制器方法的名字以"Get"、"Post"、"Put"、"Delete"、"Head"、"Options"或者"Patch"开头,那么按约定,操作支持对应的Http方法。
如果以上都不满足,这个方法支持Post操作
参数绑定。参数绑定是指Web API如何为参数创建值。绑定的默认规则如下:
简单类型直接从URI中获取
复杂类型从请求主体中获取
简单类型包括.NET Framework primitive types",加上DateTime、Decimal、Guid、String和TimeSpan。对于每个操作,最多一个参数可以从请求正文里读取。
可以重写默认绑定规则。参照WebAPI Parameter binding under the hood。
以此为背景,操作的选择算法如下:
创建一个列表,其包含了控制器上与http请求方法相匹配的所有操作
如果路由字典有"action"项,则移除其名称与此值不匹配的操作
尝试将操作参数与URI匹配,如下:
对于每个操作,获取一个简单类型的参数列表,其中绑定从URI获取的参数。排除可选参数
在该列表中,尝试在路由字典或URI查询字符串中查找每个参数名称的匹配项。匹配不区分大小写,不依赖参数顺序
选择一个操作,其列表中的每个参数都在URI存在匹配
如果不止一个操作符合这些条件,选择参数匹配最多的那个
忽略带有[NonAction]属性的操作
步骤3可能是最令人困惑的。基本思想是,参数可以从URI、请求正文或自定义绑定中获取其值。对于来自URI的参数,我们希望确保URI真的包含参数值,要么在路径(根据路由字典)里,要么在查询字符串里。
例如,考虑下面这个操作:
id参数绑定到了URI。因此,这个操作只能匹配包含"id"值的URI,无论是在路由字典里还是在查询字符串里。
可选参数除外,因为他们是可以跳过的。对于可选参数,即使绑定无法从URI获取到值也是没事的。
不同的原因导致了复杂类型是个异类。复杂类型只能通过自定义绑定来绑定到URI。但是在这种情况下,框架不能事先知道参数是否绑定到了特定的URI。为了找出来,它需要调用自定义绑定过程。选择算法的目标是在调用任何绑定过程之前,从静态描述中选择一个操作。因此,复杂类型从匹配算法中排除。
选择操作后,将调用所有参数绑定。
总结:
该操作必须与HTTP方法匹配
操作名称必须与路由字典中的"action"项匹配(如果存在)
对于操作的每个参数,如果参数是从URI中取出来的,就必须在路由字典或URI查询字符串中找到该参数的名称(排除复杂类型参数和可选参数)
尝试匹配最多数目的参数,最佳匹配可能是没有参数的方法
##扩展示例
路由:
控制器:
HTTP 请求:
###路由匹配
和这个URI匹配的路由是“DefaultApi”。路由字典包含以下内容:
controller: "products"
id: "1"
路由字典没有包含查询字符串参数(“version” 和 “details”)但是在操作选择阶段仍会参考它们。
###选择控制器
根据路由字典里的"controller"项,控制器类型是
###操作选择
HTTP 请求是 GET 请求。控制器操作中支持 GET 的是
下一步,我们尝试匹配操作的参数名,只看 GET 操作。
注意 ,没有考虑
两者相较,
id = 1
version = 1.5
注意,尽管选择算法中没有用到version,但是其参数值依然从URI查询字符串中取到。
##扩展知识点
原文地址:https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
原文名称:Routing and Action Selection in ASP.NET Web API
原文作者:Mike Wasson
这篇文章描述了ASP.NET Web API 怎么将一个HTTP请求转换成指定控制器的指定操作。
Note:有关路由的高层概述,查看Routing in ASP.NET Web API.
本文关注的是路由过程的细节。如果你创建了一个Web API 项目并且发现一些请求不能像你预期的那样被路由,希望本文可以提供一些帮助。
路由有三个主要的部分:
将URI和路由模板互相匹配
选择控制器
选择操作
你可以将其中的任意一个过程替换为自定义行为。本文主要描述默认行为。在文末,我标记了你可以自定义的地方。
##路由模板
一个路由模板看起来很像URI路径,但是它有被大括号包裹的占位符:
"api/{controller}/public/{category}/{id}"
当你创建一个路由的时候,可以给占位符提供默认值:
defaults: new { category = "all" }
你也可以提供约束,来限制URI怎么匹配占位符
constraints: new { id = @"\d+" } //当id是数字是才匹配成功
框架尝试将URI和模板匹配,模板中的字面量必需一字不差,占位符可以是任何值(除非指定了约束)。框架不会匹配URI的所有内容,比如主机名、查询参数。框架会选择路由表中匹配的第一个路由。
这里有两个特殊的占位符:"{controller}" 和 "{action}"
"{controller}" 提供了控制器的名字。
"{action}" 提供了操作的名字。在Web API里,惯例会省略"{action}"。
###默认值
如果提供了默认值,那些缺省了相应部分的URI也可以匹配成功。例如:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{category}",
defaults: new { category = "all" });
"
http://localhost/api/products"就可以匹配到这个路由。"{category}"字段会被分配默认值"all"。
###路由字典
如果框架找到了与URI匹配的路由,它会创建一个字典,其中包含了每个占位符的值。键是占位符名称,不包括大括号。值取自URI路径或者默认值。这个字典被存储在IHttpRouteData对象里。
路由匹配的过程中,"{controller}"和"{action}"这两个特殊的占位符与其他占位符的处理方式一样,以键值的形式存储在字典中。
默认值可以设置为RouteParameter.Optional。如果一个占位符被分配到了这个值,它的值就不会被添加到路由字典里。例如:
routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{category}/{id}", defaults: new { category = "all", id = RouteParameter.Optional } );
对于"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"
##选择控制器
控制器的选择,由IHttpControllerSelector.SelectController方法掌管。这个方法获取HttpRequestMessage实例作为参数,返回一个HttpControllerDescriptor对象。其默认实现由DefaultHttpControllerSelector类提供。这个类使用了一个简单的算法:
在路由字典里查找键"controller"
获取此项的值,并追加字符串"Controller"以获得控制器类型名称
查找具有此类型名称的Web API控制器
举个例子,如果路由字典包含键值对 "controller" = "products",然后控制器类型就是"ProductsController"。如果没有匹配的类型或者匹配到了多个,框架会给客户端返回一个错误。
第三步,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口获取所有的Web API控制器类型。IHttpControllerTypeResolver的默认实现,返回(a)实现了IHttpController、(b)不是抽象的、(c)名称以"Controller"结尾的所有公共类。
##操作选择
选择了控制器之后,框架就会调用IHttpActionSelector.SelectAction方法来选择操作。这个方法接收HttpControllerContext对象并返回HttpActionDescriptor对象。
其默认实现由ApiControllerActionSelector类提供。
若要选择一个操作,它将查看以下内容:
请求的Http方法
路由模板中的"{action}"占位符(如果存在的话)
控制器上所有操作的参数
在查看选择算法之前,我们需要了解一些关于控制器操作的事情。
控制器上的哪个方法会被当做“操作”?[b]当选择一个操作时,框架只看控制器上的公共方法。此外,还排除了一些具有“特殊名称”的方法(构造函数、事件、运算符重载等)和从ApiController[/b]类继承的方法。
Http 方法。框架只选择与请求的Http方法匹配的操作,如下所示:
可以使用属性指定Http方法:AcceptVerbs,HttpDelete,HttpGet,HttpHead,HttpPatch,HttpPost,或者HttpPut。
另外,如果控制器方法的名字以"Get"、"Post"、"Put"、"Delete"、"Head"、"Options"或者"Patch"开头,那么按约定,操作支持对应的Http方法。
如果以上都不满足,这个方法支持Post操作
参数绑定。参数绑定是指Web API如何为参数创建值。绑定的默认规则如下:
简单类型直接从URI中获取
复杂类型从请求主体中获取
简单类型包括.NET Framework primitive types",加上DateTime、Decimal、Guid、String和TimeSpan。对于每个操作,最多一个参数可以从请求正文里读取。
可以重写默认绑定规则。参照WebAPI Parameter binding under the hood。
以此为背景,操作的选择算法如下:
创建一个列表,其包含了控制器上与http请求方法相匹配的所有操作
如果路由字典有"action"项,则移除其名称与此值不匹配的操作
尝试将操作参数与URI匹配,如下:
对于每个操作,获取一个简单类型的参数列表,其中绑定从URI获取的参数。排除可选参数
在该列表中,尝试在路由字典或URI查询字符串中查找每个参数名称的匹配项。匹配不区分大小写,不依赖参数顺序
选择一个操作,其列表中的每个参数都在URI存在匹配
如果不止一个操作符合这些条件,选择参数匹配最多的那个
忽略带有[NonAction]属性的操作
步骤3可能是最令人困惑的。基本思想是,参数可以从URI、请求正文或自定义绑定中获取其值。对于来自URI的参数,我们希望确保URI真的包含参数值,要么在路径(根据路由字典)里,要么在查询字符串里。
例如,考虑下面这个操作:
public void Get(int id)
id参数绑定到了URI。因此,这个操作只能匹配包含"id"值的URI,无论是在路由字典里还是在查询字符串里。
可选参数除外,因为他们是可以跳过的。对于可选参数,即使绑定无法从URI获取到值也是没事的。
不同的原因导致了复杂类型是个异类。复杂类型只能通过自定义绑定来绑定到URI。但是在这种情况下,框架不能事先知道参数是否绑定到了特定的URI。为了找出来,它需要调用自定义绑定过程。选择算法的目标是在调用任何绑定过程之前,从静态描述中选择一个操作。因此,复杂类型从匹配算法中排除。
选择操作后,将调用所有参数绑定。
总结:
该操作必须与HTTP方法匹配
操作名称必须与路由字典中的"action"项匹配(如果存在)
对于操作的每个参数,如果参数是从URI中取出来的,就必须在路由字典或URI查询字符串中找到该参数的名称(排除复杂类型参数和可选参数)
尝试匹配最多数目的参数,最佳匹配可能是没有参数的方法
##扩展示例
路由:
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 Put(int id, Product value) {} }
HTTP 请求:
GET http://localhost:34701/api/products/1?version=1.5&details=1
###路由匹配
和这个URI匹配的路由是“DefaultApi”。路由字典包含以下内容:
controller: "products"
id: "1"
路由字典没有包含查询字符串参数(“version” 和 “details”)但是在操作选择阶段仍会参考它们。
###选择控制器
根据路由字典里的"controller"项,控制器类型是
ProductsController。
###操作选择
HTTP 请求是 GET 请求。控制器操作中支持 GET 的是
GetAll,
GetById和
FindProductsByName。路由字典里没有包含"action",所以我们不需要匹配操作名称。
下一步,我们尝试匹配操作的参数名,只看 GET 操作。
Action | Parameters to Match |
---|---|
GetAll | none |
GetById | "id" |
FindProductsByName | "name" |
GetById的version参数,因为它是可选的。
GetAll方法是匹配的。
GetById方法也是匹配的,因为路由字典里包含"id"。
FindProductsByName方法就不匹配了。
两者相较,
GetById方法胜出,因为它匹配了一个参数,要比没有参数的
GetAll多一个。使用以下参数值调用方法:
id = 1
version = 1.5
注意,尽管选择算法中没有用到version,但是其参数值依然从URI查询字符串中取到。
##扩展知识点
相关文章推荐
- Asp.Net Web API 2第六课——Web API路由和动作选择
- 【ASP.NET Web API教程】4.2 路由与动作选择
- Asp.Net Web API 2第六课——Web API路由和动作选择
- 【ASP.NET Web API教程】4.2 路由与动作选择
- Asp.Net Web API 2第六课——Web API路由和动作选择
- 【Web API系列教程】2.2 — ASP.NET Web API中的路由和动作选择机制
- Asp.net Web Api 路由 和 异常处理
- ASP.net 的URL路由选择(System.Web.Routing.dll)
- HttpClient + ASP.NET Web API, WCF之外的另一个选择
- WCF和ASP.NET Web API在应用上的选择
- Asp.Net之两个ListBox的列表项选择移动操作
- 返璞归真 asp.net mvc (11) - asp.net mvc 4.0 新特性之自宿主 Web API, 在 WebForm 中提供 Web API, 通过 Web API 上传文件, .net 4.5 带来的更方便的异步操作
- WCF和ASP.NET Web API在应用上的选择
- ASP.NET怎么操作DataTable实例应用
- 【转载】返璞归真 asp.net mvc (11) - asp.net mvc 4.0 新特性之自宿主 Web API, 在 WebForm 中提供 Web API, 通过 Web API 上传文件, .net 4.5 带来的更方便的异步操作
- ASP.NET MVC4 ASP.NET Web API路由规则
- asp.net TreeView安装、使用(如何将TreeView打包发布)(带CheckBox选择框的TreeView的初始化,TreeView客户端操作:选择父节点后自动选择所有子节点,子节点选择后自动选择父节点)(TreeView节点精确定位)
- ASP.NET怎么操作DataTable
- ASP.NET在后台代码实现个功能,根据选择提示用户是否继续执行操作
- 【ASP.NET Web API教程】2.1 创建支持CRUD操作的Web API