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

注:本文是【ASP.NET Web API系列教程】的一部分,如果您是第一次看本系列教程,请先看前面的内容。

4.2 Routing and Action Selection
4.2 路由与动作选择

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

By Mike Wasson | July 27, 2012
作者:Mike Wasson | 日期:2012-7-27

This article describes how ASP.NET Web API routes an HTTP request to a particular action on a controller.
本文描述ASP.NET Web API如何把一个HTTP请求路由到控制器的一个特定的方法上。

For a high-level overview of routing, see Routing in ASP.NET Web API.
关于路由的总体概述,参阅“ASP.NET Web API中的路由”(本系列教程的前一小节 — 译者注)。

This article looks at the details of the routing process. If you create a Web API project and find that some requests don’t get routed the way you expect, hopefully this article will help.
本文考察路由过程的细节。如果你创建了一个Web API项目,并发现有些请求并未按你期望的方式被路由,希望这篇文章对你会有所帮助。

Routing has three main phases:
路由有三个主要阶段:

  1. Matching the URI to a route template.
    将URI匹配到一个路由模板。
  2. Selecting a controller.
    选择一个控制器
  3. Selecting an action.
    选择一个动作。

You can replace some parts of the process with your own custom behaviors. In this article, I describe the default behavior. At the end, I note the places where you can customize the behavior.
你可以用自己的自定义行为来替换这一过程的某些部分。在本文中,我会描述默认行为。最后,我会注明可以在什么地方自定义行为。

Route Templates
路由模板

A route template looks similar to a URI path, but it can have placeholder values, indicated with curly braces:
路由模板看上去类似于一个URI路径,但它可以具有占位符,这是用花括号来指示的:

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

When you create a route, you can provide default values for some or all of the placeholders:
当创建一条路由时,可以为某些或所有占位符提供默认值:

defaults: new { category = "all" }

You can also provide constraints, which restrict how a URI segment can match a placeholder:
也可以提供约束,它限制URI片段如何与占位符匹配:

constraints: new { id = @"\d+" }   // Only matches if "id" is one or more digits.
// 用正则表达式限制片段的取值,上语句表明,id片段的值必须是一个或多个数字。
// 因此,URI中id片段必须是数字才能与这条路由匹配

The framework tries to match the segments in the URI path to the template. Literals in the template must match exactly. A placeholder matches any value, unless you specify constraints. The framework does not match other parts of the URI, such as the host name or the query parameters. The framework selects the first route in the route table that matches the URI.
框架会试图把URI路径的片段与该模板进行匹配。模板中的文字必须严格匹配。占位符可以匹配任意值,除非你指定了约束。框架不会匹配URI的其它部分,如主机名或查询字符串。框架会选择路由表中与URI匹配的第一条路由。

There are two special placeholders: "{controller}" and "{action}".
有两个特殊的占位符:“{controller}”和“{action}”。

  • "{controller}" provides the name of the controller.
    “{controller}”提供控制器名。
  • "{action}" provides the name of the action. In Web API, the usual convention is to omit "{action}".
    “{action}”提供动作名。在Web API中,通常的约定是忽略“{action}”的。

Defaults
默认值

If you provide defaults, the route will match a URI that is missing those segments. For example:
如果提供默认值,该路由将能够匹配缺少这些片段的URI。例如:

routes.MapHttpRoute(
    name: "DefaultApi", 
    routeTemplate: "api/{controller}/{category}", 
    defaults: new { category = "all" } 
);

The URI "http://localhost/api/products" matches this route. The "{category}" segment is assigned the default value "all".
URI“http://localhost/api/products”与这条路由是匹配的。“{category}”片段被赋成了默认值“all”。

Route Dictionary
路由字典

If the framework finds a match for a URI, it creates a dictionary that contains the value for each placeholder. The keys are the placeholder names, not including the curly braces. The values are taken from the URI path or from the defaults. The dictionary is stored in the IHttpRouteData object.
如果框架为一个URI找到一个匹配,它会创建一个字典,其中包含了每个占位符的值。(字典的内容是一些“键-值”对 — 译者注)。其键是不带花括号的占位符名称。其值取自URI路径或默认值。该字典被存储在IHttpRouteData对象中。

During this route-matching phase, the special "{controller}" and "{action}" placeholders are treated just like the other placeholders. They are simply stored in the dictionary with the other values.
在路由匹配阶段,“{controller}”和“{action}”占位符的处理与其它占位符的处理是一样的。只是把它们简单地用值存储在字典中。

A default can have the special value RouteParameter.Optional. If a placeholder gets assigned this value, the value is not added to the route dictionary. For example:
在默认值中可以使用特殊的RouteParameter.Optional值。如果一个占位符被赋予了这个值,则该值不会被添加到路由字典。例如:

routes.MapHttpRoute( 
    name: "DefaultApi", 
    routeTemplate: "api/{controller}/{category}/{id}", 
    defaults: new { category = "all", id = RouteParameter.Optional } 
);

For the URI path "api/products", the route dictionary will contain:
对于URI路径“api/products”,路由字典将含有:

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

由于这条URI路径中不包含id,因此,id的值将采用默认的RouteParameter.Optional,所以,路由字典中不会包含id片段的键值对 — 译者注

For "api/products/toys/123", however, the route dictionary will contain:
然而,对于“api/products/toys/123”,路由字典将含有:

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

The defaults can also include a value that does not appear anywhere in the route template. If the route matches, that value is stored in the dictionary. For example:
默认值也可以包含未出现在路由模板中的值。若这条路由匹配,则该值会被存储在路由字典中。例如:

routes.MapHttpRoute( 
    name: "Root", 
    routeTemplate: "api/root/{id}", 
    defaults: new { controller = "customers", id = RouteParameter.Optional } 
);

If the URI path is "api/root/8", the dictionary will contain two values:
如果URI路径是“api/root/8”,字典将含有两个值:

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

Selecting a Controller
选择控制器

Controller selection is handled by the IHttpControllerSelector.SelectController method. This method takes an HttpRequestMessage instance and returns an HttpControllerDescriptor. The default implementation is provided by the DefaultHttpControllerSelector class. This class uses a straightforward algorithm:
控制器选择是由IHttpControllerSelector.SelectController方法来处理的。这个方法以HttpRequestMessage实例为参数,并返回HttpControllerDescriptor。其默认实现是由DefaultHttpControllerSelector类提供的。这个类使用了一种很直接的算法:

  1. Look in the route dictionary for the key "controller".
    查找路由字典的“controller”键。
  2. Take the value for this key and append the string "Controller" to get the controller type name.
    取得这个键的值,并附加字符串“Controller”,以得到控制器的类型名。
  3. Look for a Web API controller with this type name.
    用这个类型名查找Web API控制器。

For example, if the route dictionary contains the key-value pair "controller" = "products", then the controller type is "ProductsController". If there is no matching type, or multiple matches, the framework returns an error to the client.
例如,如果路由字典的键-值对为“controller”=“products”,那么,控制器类型便为“ProductsController”。如果没有匹配类型,或有多个匹配,框架会给客户端返回一条错误。

For step 3, DefaultHttpControllerSelector uses the IHttpControllerTypeResolver interface to get the list of Web API controller types. The default implementation of IHttpControllerTypeResolver returns all public classes that (a) implement IHttpController, (b) are not abstract, and (c) have a name that ends in "Controller".
对于步骤3,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口以获得Web API控制器类型的列表。IHttpControllerTypeResolver的默认实现会返回所有符合以下条件的public类:(a)实现IHttpController的类(b)是非抽象类,且(c)名称以“Controller”结尾的类。

Action Selection
动作选择

After selecting the controller, the framework selects the action by calling the IHttpActionSelector.SelectAction method. This method takes an HttpControllerContext and returns an HttpActionDescriptor.
选择了控制器之后,框架会通过调用IHttpActionSelector.SelectAction方法来选择动作。这个方法以HttpControllerContext为参数,并返回HttpActionDescriptor

The default implementation is provided by the ApiControllerActionSelector class. To select an action, it looks at the following:
默认实现是由ApiControllerActionSelector类提供的。为了选择一个动作,会查找以下方面:

  • The HTTP method of the request.
    请求的HTTP方法。
  • The "{action}" placeholder in the route template, if present.
    路由模板中的“{action}”占位符(如果有)。
  • The parameters of the actions on the controller.
    控制器中动作的参数。

Before looking at the selection algorithm, we need to understand some things about controller actions.
在查找选择算法之前,我们需要理解控制器动作的一些事情。

Which methods on the controller are considered "actions"? When selecting an action, the framework only looks at public instance methods on the controller. Also, it excludes "special name" methods (constructors, events, operator overloads, and so forth), and methods inherited from the ApiController class.
控制器中的哪些方法被看成为是“动作”?当选择一个动作时,框架只考察控制器的public实例方法。而且,它会排除“special name"特殊名称”的方法(构造器、事件、操作符重载等等),以及继承于ApiController类的方法。

这里按原文的含义似乎是要排除API控制器中的public方法,但译者认为,框架会把API控制器中的public方法看成是动作 — 译者注

HTTP Methods. The framework only chooses actions that match the HTTP method of the request, determined as follows:
HTTP方法。框架只会选择与请求的HTTP方法匹配的动作,确定如下:

  1. You can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
    你可以用注解属性AcceptVerbs、HttpDelete、HttpGet、HttpHead、HttpOptions、HttpPatch、HttpPost、或HttpPut来指定HTTP方法(这段文字说明,你可以在方法上用这些注解属性进行标注,以指定该方法用于处理哪一种HTTP请求。通过这种标注,方法的命名可以不遵循下一条的约定 — 译者注)。
  2. Otherwise, if the name of the controller method starts with "Get", "Post", "Put", "Delete", "Head", "Options", or "Patch", then by convention the action supports that HTTP method.
    否则,如果控制器方法名称以“Get”、“Post”、“Put”、“Delete”、“Head”、“Options”、或“Patch”开头,那么,按照约定,该动作支持相应的HTTP方法。
  3. If none of the above, the method supports POST.
    如果以上都不是(即,既未用第一条的办法进行标注,又未用第二条的方法命名约定 — 译者注),则该方法支持POST。

Parameter Bindings. A parameter binding is how Web API creates a value for a parameter. Here is the default rule for parameter binding:
参数绑定。参数绑定是指Web API如何创建参数值。以下是参数绑定的默认规则:

  • Simple types are taken from the URI.
    简单类型取自URI。
  • Complex types are taken from the request body.
    复合类型取自请求体。

Simple types include all of the .NET Framework primitive types, plus DateTime, Decimal, Guid, String, and TimeSpan. For each action, at most one parameter can read the request body.
简单类型包括所有“.NET 框架简单类型”,另外还有,DateTime、Decimal、Guid、String和TimeSpan。对于每一个动作,最多只有一个参数可以读取请求体。

It is possible to override the default binding rules. See WebAPI Parameter binding under the hood.
也可以重写这种默认的绑定规则。参见WebAPI Parameter binding under the hood(作者的一篇博客文章 — 译者注)。

With that background, here is the action selection algorithm.
在这种背景下,动作选择算法如下:

  1. Create a list of all actions on the controller that match the HTTP request method.
    创建该控制器中与HTTP请求方法匹配的所有动作的列表(这一步属于HTTP请求方法匹配,即,从已选定的控制器中挑出了与请求类型匹配的动作方法,例如,对于GET请求,于是只挑出处理GET请求的那些方法 — 译者注)。
  2. If the route dictionary has an "action" entry, remove actions whose name does not match this value.
    如果路由字典有“action”条目,(从该列表)除去与该条目值不匹配的动作(这一步属于方法名称匹配,即,在上一步基础上进一步挑出其中与路由字典的action键值匹配的动作方法 — 译者注)。
  3. Try to match action parameters to the URI, as follows:
    试图将动作参数与该URI匹配,办法如下(这一步属于参数匹配 — 译者注):
    1. For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. Exclude optional parameters.
      针对每个动作,获得简单类型的参数列表,这是绑定得到URI参数的地方。该列表不包括可选参数(提取方法的参数名称 — 译者注)。
    2. From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are case insensitive and do not depend on the parameter order.
      根据这个列表,在路由字典或是在URI查询字符串中,试着为每个参数名找到一个匹配。匹配是大小写不敏感的,且与参数顺序无关(为各个方法参数匹配一个值 — 译者注)。
    3. Select an action where every parameter in the list has a match in the URI.
      选择一个动作,其列表中的每个参数都在这个URI中获得一个匹配(选出满足条件的动作 — 译者注)。
    4. If more that than one action meets these criteria, pick the one with the most parameter matches.
      如果满足这些条件的动作不止一个,选用参数匹配最多的一个(进一步筛选动作 — 译者注)。
  4. Ignore actions with the [NonAction] attribute.
    忽略用[NonAction]注解属性标注的动作。

Step #3 is probably the most confusing. The basic idea is that a parameter can get its value either from the URI, from the request body, or from a custom binding. For parameters that come from the URI, we want to ensure that the URI actually contains a value for that parameter, either in the path (via the route dictionary) or in the query string.
第3步可能会让人困扰。其基本思想是,可以从URI、或请求体、或一个自定义绑定来获取参数值(这里指出了方法参数的来源 — 译者注)。对于来自URI的参数,我们希望确保URI在其路径(通过路由字典)或查询字符串中实际包含了一个用于此参数的值。

For example, consider the following action:
例如,考虑以下动作:

public void Get(int id)

The id parameter binds to the URI. Therefore, this action can only match a URI that contains a value for "id", either in the route dictionary or in the query string.
其id参数绑定到URI。因此,这个动作只能匹配在路由字典或查询字符串中包含了“id”值的URI。

Optional parameters are an exception, because they are optional. For an optional parameter, it's OK if the binding can't get the value from the URI.
可选参数是一个例外,因为它们是可选的。对于可选参数,如果绑定不能通过URI获取它的值,是没关系的。

Complex types are an exception for a different reason. A complex type can only bind to the URI through a custom binding. But in that case, the framework cannot know in advance whether the parameter would bind to a particular URI. To find out, it would need to invoke the binding. The goal of the selection algorithm is to select an action from the static description, before invoking any bindings. Therefore, complex types are excluded from the matching algorithm.
复合类型是另一种原因的例外。复合类型只能通过自定义绑定来绑定到URI。但在这种情况下,框架不能预知该参数是否会绑定到一个特定的URI。为了找出来,框架需要调用绑定。选择算法的目的是在调用绑定之前根据静态描述来选择一个动作。因此,复合类型是属于匹配算法之外的。

After the action is selected, all parameter bindings are invoked.
动作选择之后,会调用所有参数绑定。

Summary:
小结:

  • The action must match the HTTP method of the request.
    动作必须匹配请求的HTTP方法。
  • The action name must match the "action" entry in the route dictionary, if present.
    动作名必须匹配路由字典中的“action”条目,如果有。
  • For every parameter of the action, if the parameter is taken from the URI, then the parameter name must be found either in the route dictionary or in the URI query string. (Optional parameters and parameters with complex types are excluded.)
    对于动作的各个参数,如果参数取自URI,那么该参数名必须在路由字典或URI查询字符串中能够被找到。(可选参数和复合类型的参数不包括在内。)
  • Try to match the most number of parameters. The best match might be a method with no parameters.
    试图匹配最多数目的参数。最佳匹配可能是一个无参数方法(意即,框架按最多参数匹配来选择动作,而不是按最佳匹配来选择 — 译者注)。

Extended Example
扩展示例

Routes:
路由

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 } 
);

Controller:
控制器:

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 request:
HTTP请求:

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

Route Matching
路由匹配

The URI matches the route named "DefaultApi". The route dictionary contains the following entries:
该URI与名为“DefaultApi”路由匹配。路由字典包含以下条目:

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

The route dictionary does not contain the query string parameters, "version" and "details", but these will still be considered during action selection.
该路由字典并未包含查询字符串参数“version”和“details”,但这些将在动作选择期间考虑。

Controller Selection
控制器选择

From the "controller" entry in the route dictionary, the controller type is ProductsController.
根据路由字典中的“controller”条目,控制器类型是ProductsController。

Action Selection
选择动作

The HTTP request is a GET request. The controller actions that support GET are GetAll, GetById, and FindProductsByName. The route dictionary does not contain an entry for "action", so we don’t need to match the action name.
该HTTP请求是一个GET请求。支持GET的控制器动作是GetAllGetByIdFindProductsByName。路由字典不包含“action”条目,因此不需要匹配动作名。

Next, we try to match parameter names for the actions, looking only at the GET actions.
下一步,会试图匹配这些动作的参数名,只考查GET动作。

Action
动作
Parameters to Match
要匹配的参数
GetAll None(无)
GetById "id"
FindProductsByName "name"

Notice that the version parameter of GetById is not considered, because it is an optional parameter.
注意,不会考虑GetByIdversion参数,因为它是一个可选参数。

The GetAll method matches trivially. The GetById method also matches, because the route dictionary contains "id". The FindProductsByName method does not match.
GetAll方法非常匹配(这是最佳匹配,但不是最终选择 — 译者注)。GetById方法也匹配,因为路由字典包含了“id”。FindProductsByName方法不匹配。

The GetById method wins, because it matches one parameter, versus no parameters for GetAll. The method is invoked with the following parameter values:
GetById方法是赢家,因为它匹配了一个参数,而GetAll无参数。该方法将以以下参数值被调用:

  • id = 1
  • version = 1.5

Notice that even though version was not used in the selection algorithm, the value of the parameter comes from the URI query string.
注意,虽然version未被用于选择算法,但该参数值会取自URI查询字符串。

Extension Points
扩展点

Web API provides extension points for some parts of the routing process.
Web API为路由过程的某些部分提供了扩展点。

Interface
接口
Description
描述
IHttpControllerSelector Selects the controller.
选择控制器。
IHttpControllerTypeResolver Gets the list of controller types. The DefaultHttpControllerSelector chooses the controller type from this list.
获取控制器类型列表。DefaultHttpControllerSelector从该列表选择控制器。
IAssembliesResolver Gets the list of project assemblies. The IHttpControllerTypeResolver interface uses this list to find the controller types.
获取项目程序集列表。IHttpControllerTypeResolver接口用该列表查找控制器类型。
IHttpControllerActivator Creates new controller instances.
创建控制器新实例。
IHttpActionSelector Selects the action.
选择动作。
IHttpActionInvoker Invokes the action.
调用动作。

To provide your own implementation for any of these interfaces, use the Services collection on the HttpConfiguration object:
要为以上任一接口提供自己的实现,可使用HttpConfiguration对象的Services集合:

var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));

 

看完此文如果觉得有所收获,请给个推荐

 

posted @ 2012-12-04 12:38 r01cn 阅读(...) 评论(...) 编辑 收藏