路由和 ASP.NET Web API 中的動作選取Routing and Action Selection in ASP.NET Web API

藉由Mike Wassonby Mike Wasson

這篇文章說明 ASP.NET Web API 將 HTTP 要求路由至特定動作的控制站上的方式。This article describes how ASP.NET Web API routes an HTTP request to a particular action on a controller.

Note

路由的高階概觀,請參閱 < ASP.NET Web API 中的路由For a high-level overview of routing, see Routing in ASP.NET Web API.

這篇文章探討路由的處理程序的詳細資料。This article looks at the details of the routing process. 如果您建立 Web API 專案時,發現其中某些要求不會路由傳送您預期的方式,希望這篇文章會幫助。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.

路由有三個主要階段:Routing has three main phases:

  1. 比對路由範本的 URI。Matching the URI to a route template.
  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

路由範本看起來類似的 URI 路徑,但它可以有預留位置值,以大括號:A route template looks similar to a URI path, but it can have placeholder values, indicated with curly braces:

"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" }

您也可以提供條件約束,限制如何 URI 區段可以比對的預留位置:You can also provide constraints, which restrict how a URI segment can match a placeholder:

constraints: new { id = @"\d+" }   // Only matches if "id" is one or more digits.

此架構會嘗試比對中範本的 URI 路徑的區段。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. 此架構不符合的 URI,例如主機名稱或查詢參數的其他部分。The framework does not match other parts of the URI, such as the host name or the query parameters. Framework 會選取符合 URI 的路由表中的第一個路由。The framework selects the first route in the route table that matches the URI.

有兩個特殊的預留位置:"{controller}"和"{action}"。There are two special placeholders: "{controller}" and "{action}".

  • "{controller}"提供的控制器名稱。"{controller}" provides the name of the controller.
  • "{action}"提供動作的名稱。"{action}" provides the name of the action. 在 Web API 中,一般的慣例是省略"{action}"。In Web API, the usual convention is to omit "{action}".

預設值Defaults

如果您提供預設值時,路由會符合缺少這些區段的 URI。If you provide defaults, the route will match a URI that is missing those segments. 例如:For example:

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

Urihttp://localhost/api/products/allhttp://localhost/api/products符合上述的路由。The URIs http://localhost/api/products/all and http://localhost/api/products match the preceding route. 在後者的 URI、 遺漏{category}區段會指派預設值allIn the latter URI, the missing {category} segment is assigned the default value all.

路由字典Route Dictionary

如果架構找到相符項目 uri,它會建立字典,其中包含每個預留位置的值。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. 值取自 URI 的路徑或預設值。The values are taken from the URI path or from the defaults. 字典會儲存在IHttpRouteData物件。The dictionary is stored in the IHttpRouteData object.

在此路由比對 」 階段中,特殊"{controller}"和"{action}"預留位置的處理就像其他版面配置。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.

預設值可以有特殊值RouteParameter.OptionalA 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:

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

URI 路徑 「 api/產品 」 中,將會包含路由字典:For the URI path "api/products", the route dictionary will contain:

  • 控制站: 「 產品 」controller: "products"
  • 類別目錄: [全部]category: "all"

"Api/產品/toys/123",不過,路由字典會包含:For "api/products/toys/123", however, the route dictionary will contain:

  • 控制站: 「 產品 」controller: "products"
  • 類別: 「 玩具 」category: "toys"
  • 識別碼:"123"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 }
);

如果 URI 的路徑是"api/root/8",字典會包含兩個值:If the URI path is "api/root/8", the dictionary will contain two values:

  • 控制站: 「 客戶 」controller: "customers"
  • 識別碼:"8"id: "8"

選取控制站Selecting a Controller

控制器選取項目由IHttpControllerSelector.SelectController方法。Controller selection is handled by the IHttpControllerSelector.SelectController method. 這個方法會採用HttpRequestMessage執行個體,並傳回HttpControllerDescriptorThis method takes an HttpRequestMessage instance and returns an HttpControllerDescriptor. 預設實作由提供DefaultHttpControllerSelector類別。The default implementation is provided by the DefaultHttpControllerSelector class. 這個類別會使用簡單的演算法:This class uses a straightforward algorithm:

  1. 查看路由字典的索引鍵"controller"。Look in the route dictionary for the key "controller".
  2. 此索引鍵的值,並附加 「 控制器 」 以取得控制器的型別名稱的字串。Take the value for this key and append the string "Controller" to get the controller type name.
  3. 使用此型別名稱的 Web API 控制器的外觀。Look for a Web API controller with this type name.

比方說,如果路由字典包含索引鍵 / 值組"controller"= 「 產品 」,則控制器類型是"ProductsController 」。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.

步驟 3 中,如DefaultHttpControllerSelector會使用IHttpControllerTypeResolver介面,以取得 Web API 控制器型別的清單。For step 3, DefaultHttpControllerSelector uses the IHttpControllerTypeResolver interface to get the list of Web API controller types. 預設實作IHttpControllerTypeResolver (a) 實作會傳回所有公用類別IHttpController,(b) 都不抽象,且 (c) 中"Controller"結尾的名稱。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".

動作選取Action Selection

選取控制站之後, 此架構,請選取藉由呼叫的動作IHttpActionSelector.SelectAction方法。After selecting the controller, the framework selects the action by calling the IHttpActionSelector.SelectAction method. 這個方法會採用HttpControllerContext ,然後傳回HttpActionDescriptorThis method takes an HttpControllerContext and returns an HttpActionDescriptor.

預設實作由提供ApiControllerActionSelector類別。The default implementation is provided by the ApiControllerActionSelector class. 若要選取的動作,它會查看下列:To select an action, it looks at the following:

  • 要求的 HTTP 方法。The HTTP method of the request.
  • 在路由範本中,如果有的話"{action}"預留位置。The "{action}" placeholder in the route template, if present.
  • 在控制器上的動作參數。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. 此外,它會排除「 特殊名稱 」 (建構函式、 事件、 運算子多載,等等) 的方法和方法繼承自ApiController類別。Also, it excludes "special name" methods (constructors, events, operator overloads, and so forth), and methods inherited from the ApiController class.

HTTP 方法。HTTP Methods. 架構只會選擇符合要求的 HTTP 方法,取決於下列動作:The framework only chooses actions that match the HTTP method of the request, determined as follows:

  1. 您可以使用屬性來指定 HTTP 方法:AcceptVerbsHttpDeleteHttpGetHttpHeadHttpOptionsHttpPatchHttpPost,或HttpPutYou can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
  2. 否則,如果以"Get"、"Post"、"Put"、"Delete"、"Head"、 [選項] 或 「 修補 」 開頭的控制器方法的名稱,然後依照慣例動作支援該 HTTP 方法。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.
  3. 如果以上皆非,方法支援 POST。If none of the above, the method supports POST.

參數繫結。Parameter Bindings. 參數繫結是 Web API 建立參數的值的方式。A parameter binding is how Web API creates a value for a parameter. 以下是參數繫結的預設規則:Here is the default rule for parameter binding:

  • 簡單型別取自 URI。Simple types are taken from the URI.
  • 複雜型別會從要求主體。Complex types are taken from the request body.

簡單的型別包含的所有.NET Framework 基本型別,再加上DateTime十進位Guid字串,並TimeSpanSimple 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.

Note

可以覆寫預設繫結規則。It is possible to override the default binding rules. 請參閱WebAPI 參數繫結,在幕後See WebAPI Parameter binding under the hood.

有了此背景,以下是動作選取演算法。With that background, here is the action selection algorithm.

  1. 符合 HTTP 要求方法的控制站上建立之所有動作的清單。Create a list of all actions on the controller that match the HTTP request method.

  2. 如果路由字典中有 「 動作 」 項目,將移除其名稱不符合此值的動作。If the route dictionary has an "action" entry, remove actions whose name does not match this value.

  3. 嘗試比對 URI、 動作參數,如下所示:Try to match action parameters to the URI, as follows:

    1. 針對每個動作中,取得一份是簡單型別,其中會將繫結參數來自 URI 的參數。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.
    2. 從這份清單中,嘗試尋找名稱相符的每個參數,在路由字典或 URI 查詢字串中。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.
    3. 選取清單中的每個參數具有相符項目之 URI 中的其中一個動作。Select an action where every parameter in the list has a match in the URI.
    4. 如果更該動作符合這些準則,挑選其中的大部分參數相符項目。If more that one action meets these criteria, pick the one with the most parameter matches.
  4. 忽略動作 [NonAction] 屬性。Ignore actions with the [NonAction] attribute.

步驟 #3 是可能最容易造成混淆。Step #3 is probably the most confusing. 基本概念是來自 URI,從要求主體中,或是從自訂繫結參數可取得其值。The basic idea is that a parameter can get its value either from the URI, from the request body, or from a custom binding. 對於來自 URI 的參數,我們想要確定該 URI 實際上包含該參數,在 (透過路由字典) 的路徑或查詢字串中的值。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.

例如,請考慮下列動作:For example, consider the following action:

public void Get(int id)

識別碼參數繫結至的 URI。The id parameter binds to the URI. 因此,此動作可以只比對 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.

選擇性參數會是例外狀況,因為它們是選擇性。Optional parameters are an exception, because they are optional. 選擇性參數,就可以繫結無法從 URI 取得的值。For an optional parameter, it's OK if the binding can't get the value from the URI.

複雜型別會因其他原因的例外狀況。Complex types are an exception for a different reason. 透過自訂繫結的複雜型別只能繫結至的 URI。A complex type can only bind to the URI through a custom binding. 但在此情況下,架構無法事先知道參數會繫結至特定的 URI。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.

選取動作之後,會叫用所有的參數繫結。After the action is selected, all parameter bindings are invoked.

摘要: Summary:

  • Action 必須符合要求的 HTTP 方法。The action must match the HTTP method of the request.
  • 動作名稱必須符合 「 動作 」 中的項目路由字典,如果有的話。The action name must match the "action" entry in the route dictionary, if present.
  • 每個參數的動作,如果參數取自 URI,然後參數名稱必須找到路由字典或 URI 查詢字串中。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.)
  • 嘗試比對參數最大數目。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 要求:HTTP request:

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

路由相符Route Matching

URI 比對名為"DefaultApi"的路由。The URI matches the route named "DefaultApi". 路由字典包含下列項目:The route dictionary contains the following entries:

  • 控制站: 「 產品 」controller: "products"
  • 識別碼:"1"id: "1"

路由字典不包含查詢字串參數,"version"和"details",但這些仍然會被視為動作選取的期間。The route dictionary does not contain the query string parameters, "version" and "details", but these will still be considered during action selection.

控制器選取項目Controller Selection

從路由字典中的 「 控制器 」 項目,是控制器類型ProductsControllerFrom the "controller" entry in the route dictionary, the controller type is ProductsController.

動作選取Action Selection

HTTP 要求是 GET 要求。The HTTP request is a GET request. 支援 GET 的控制器動作GetAllGetById,和FindProductsByNameThe 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.

接下來,我們嘗試比對的動作,參數名稱顯見光看 GET 動作。Next, we try to match parameter names for the actions, looking only at the GET actions.

動作Action 要比對參數Parameters to Match
GetAll nonenone
GetById "id""id"
FindProductsByName "name""name"

請注意,版本參數GetById不是,因為它是選擇性的參數。Notice that the version parameter of GetById is not considered, because it is an optional parameter.

GetAll方法符合透過極簡方式。The GetAll method matches trivially. GetById方法也會比對,因為路由字典包含 「 識別碼 」。The GetById method also matches, because the route dictionary contains "id". FindProductsByName方法不符。The FindProductsByName method does not match.

GetById Wins 方法,因為它會比對一個參數,也沒有參數與GetAllThe GetById method wins, because it matches one parameter, versus no parameters for GetAll. 叫用方法時,使用下列參數值:The method is invoked with the following parameter values:

  • id = 1id = 1
  • 版本= 1.5version = 1.5

請注意,即使版本未使用在選取演算法參數的值來自 URI 查詢字串。Notice that even though version was not used in the selection algorithm, the value of the parameter comes from the URI query string.

擴充點Extension Points

Web API 路由的處理程序的某些部分來提供擴充點。Web API provides extension points for some parts of the routing process.

介面Interface 描述Description
IHttpControllerSelectorIHttpControllerSelector 選取的控制器。Selects the controller.
IHttpControllerTypeResolverIHttpControllerTypeResolver 取得控制器型別的清單。Gets the list of controller types. DefaultHttpControllerSelector從這份清單中選擇 控制器類型。The DefaultHttpControllerSelector chooses the controller type from this list.
IAssembliesResolverIAssembliesResolver 取得專案的組件清單。Gets the list of project assemblies. IHttpControllerTypeResolver介面使用這份清單來尋找控制器類型。The IHttpControllerTypeResolver interface uses this list to find the controller types.
IHttpControllerActivatorIHttpControllerActivator 建立新的控制器執行個體。Creates new controller instances.
IHttpActionSelectorIHttpActionSelector 選取的動作。Selects the action.
IHttpActionInvokerIHttpActionInvoker 叫用動作。Invokes the action.

若要提供您自己的實作這些介面,使用Services收集HttpConfiguration物件:To provide your own implementation for any of these interfaces, use the Services collection on the HttpConfiguration object:

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