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

 

看完此文如果覺得有所收獲,請給個推薦

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM