?
译自: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项目并发现那些不想被路由的请求,那么希望本文可以帮助到你。
路由有三个主要的阶段:
你还可以使用自定义实现来替换路由某部分处理过程。本文中,我将描述默认的行为。文末,我将指出哪些地方是可以扩展自定义行为的。
?
?
一个路由模板看起来和URI路径很相似,区别是路由模板拥有大括号标记的占位符:
"api/{controller}/public/{category}/{id}"
当创建一个路由映射时,可以为部分或者全部占位符提供默认值:
defaults: new { category = "all" }
也可以设置约束,用来限制URI段中占位符的匹配方式:
constraints: new { id = @"\d+" } // 只能匹配一个或多个数字。
路由框架会尝试匹配模板里面的URI路径。而纯文本字面量则必须全匹配。默认情况下,占位符将会匹配任何字符串,除非指定一个约束(一般是正则表达式)。路由框架并不会匹配URI的其他部分,比如主机名或者查询参数。路由框架将会在路由表中选择第一个匹配到路由。
有两个特殊的占位符:"{controller}" 和 "{action}"。
如果为占位符设置了默认值,那么路由将会匹配丢失的这些URI片段,选择默认值进行来填充。例如:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{category}",
defaults: new { category = "all" }
);
URI"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 }
);
对于URI路径"api/products",路由字典包含:
对于"api/products/toys/123",则路由字典将包含:
默认值也可以设置为没有包含在路由模板中的值。如果产生匹配,那么此默认值将会存储在字典中。例如:
routes.MapHttpRoute(
name: "Root",
routeTemplate: "api/root/{id}",
defaults: new { controller = "customers", id = RouteParameter.Optional }
);
如果URI路径是"api/root/8",那么字典将包含两个值:
?
?
使用IHttpControllerSelector.SelectController方法来处理控制器的选择。SelectController方法输入一个HttpRequestMessage?的实例,返回一个HttpControllerDescriptor。 DefaultHttpControllerSelector?类是IHttpControllerSelector的默认实现,其使用了一个简单的算法:
例如:如果路由字典包含键值对"controller" = "products",那么控制器的类型为" ProductsController"。如果没有匹配的类型,或者有多个匹配,则路由框架会返回一个错误给客户端。
对于步骤3,DefaultHttpControllerSelector?使用IHttpControllerTypeResolver?接口来获取Web API控制器类型列表。
IHttpControllerTypeResolver?的默认实现是——返回所有:
(a)实现了IHttpController接口,
(b)不是抽象类,
(c)以"Controller"结尾
的公共类。
?
?
控制器选择了之后,路由框架通过调用IHttpActionSelector.SelectAction方法选择action。SelectAction方法输入一个HttpControllerContext?并返回一个HttpActionDescriptor。
IHttpActionSelector的默认实现是ApiControllerActionSelector?类。以下执行 Action选择:
在看action选择算法之前,我们需要进一步理解控制器的action.
控制器上的哪些方法被当做"actions"?当action选择时,路由框架只关心控制器中的公有的实例方法。当然,要排除"特殊名称"的方法(如:构造函数,事件,运算符重载,等等),以及从ApiController类继承的方法。
HTTP 方法。路由框架只选择匹配请求的HTTP方法,判断如下:
参数绑定。参数绑定是Web API为参数创建一个值的过程。以下是参数绑定的默认规则:
简单类型包括.NET框架的原生类型,再加上DateTime,Decimal,Guide,String和TimeSpan。对每一个action,最多可以从请求主体中读取一个参数。
可能需要重写默认的绑定规则。请看?WebAPI Parameter binding under the hood。
有了以上的背景知识以后,以下是Action选择算法。
步骤#3大概是最疑惑的。基本的想法是一个参数可以从URI、请求主体、自定义绑定,获取到值。对于从URI获取到的参数,我们要确保URI实际只包含一个参数对应的值,或者在路径(通过路由字典获得)中,或者在查询字符串中。
例如,考虑如下action:
public void Get(int id)
id参数 绑定到URI。因此,此action只能匹配到包含"id"值的URI, 或者在路由字典中有"id",或者查询字符串中有id。
可选参数是可以被忽略,因为他们是可选的。对于一个可选参数,如果从URI中获取不到值,也是允许的。
有很多原因表明,复杂类型是一个例外。复杂类型只能通过自定义绑定绑定到URI。但在这种情况下,路由框架预先不知道参数是否将绑定到特定的URI上。为了确认是否可以绑定,将会直接调用自定义绑定。选择算法的目标是在调用任何绑定之前,从静态的描述中选择出一个action。因此,复杂类型被排除在匹配算法之外。
在action选择之后,所有参数绑定被调用。
总结:
?
?
路由映射:
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
此URI匹配名为"DefaultApi"的路由映射。路由字典包含两项:
路由字典中不包含查询字符串参数——"version" and "details",但这两个参数在action选择时仍会被考虑。
从路由字典中的"controller"项,控制器类型的ProductsController。
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?方法用以下参数值被调用:
?
请注意,尽管选择算法中不使用version参数,但是version的值来自URI查询字符串。
?
?
?
?
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));
原文:http://www.cnblogs.com/pengzhen/p/4904648.html