AspNetCore.Docs/aspnet/web-api/overview/web-api-routing-and-actions/routing-and-action-selectio...

235 lines
12 KiB
Markdown
Raw Normal View History

merge aspnetmigration to master (#2668) * Initial aspnet migration * Updating legacy urls * Clearing .gitignore file Clearing .gitignore file because this isnt a normal .net application where we want to strip away debug files, etc. We want to include everything under these folders. * Updating webhooks toc reference * Removing header debug links * Updating webhooks toc * Updating double quotes in metadata to use single quotes * Moving all code blocks to external files * Updating newlines for step-by-step * Fixing indent problem on some code blocks * Fixing newlines in alt attribute for images; Fixing some missing code block references * Resyncing with live content * Refreshing content from production * Trying to get pdf to generate for our aspnetmigration branch * Update .openpublishing.publish.config.json * Refresh from prod; Removing some legacy urls for pages that shouldnt have it * Updating index pages and removing legacy urls * Updating warning and caution notes * Removing downloads * remove aspnet from exclude list (#2549) * First pass at language detection * Updating author and adding in msc.type * Updating code blocks * Updating note styles * Fixing note styles * Updating docfx.json file to allow pdfs and gifs * Fixing note stylings for bold notes * Updating docfx.json to allow exe, zip and wmv files * Fixing note styles with period; Fixing downloads links * Fixing code blocks for razor cs and vb * Fixing more downloads links; Fixing a few code blocks * Removing   html entity * Fixing some more note stylings * Syncing with prod * Fixing issues with content * Rebuilding toc file * Adding back in files accidentally deleted * Fixing some security notes * Fixing some note styles * Updating unknown code blocks * Updating article * Fixing link * Fixing link * Fixing link * Fixing invalid characters * preliminary toc changes * update toc * fix toc folder with only one link * Fixing extra heading * Fixing articles * Reworking ajax pages * Fixing encoding issues * Updating markup in articles * Fixing space * Fixing spacing issues with links * Fixing note styles * Fixing inline note styles * Fixing missing image * Adding space * Rolling back gitignore file and adding a new one for /aspnet * Fixing some code blocks * Updating code block language * Renaming file * Updating code language * Fixing code blocks * Fixing code blocks * Fixing spaces before 'using' * Fixing cs to js * Changing Note type * Updating broken reference * Replacing headings with bolds under notes/tips, etc * Fixing markdown for pipes * Another attempted to fix pipe characters * Fixing markdown for pipes * remove text about being thread-safe (#2630) * Fixing spacing issue with list * Trying to fix pipe issue * new how to choose doc * move choose doc to core folder * add api ref * fix link * Adding in ms.assetid * Removing \ufeff * fix link * link to mvc intro instead of webhooks * add scenarios * put core first, vertical orientation for scenarios * reorganize toc, make overview work like core version * fix yaml * fix broken links * Adding space * add download link * tweak tables * eliminate images * eliminate images 2 * tweak scenario section headings * add link to core in asp.net overview * Removing gears * Updating table * Updating code block languages * fix urls (#2663) * Removing embedded in-article TOC from top of articles * fix urls (#2666) * fix urls * fix urls * Removing embedded in-article TOC from top of articles * Revert "Removing embedded in-article TOC from top of articles" This reverts commit ff1c3ccdf1cf2d705e0bb040144a10fa130796f6. * Revert "Removing embedded in-article TOC from top of articles" This reverts commit 17c37c726d930ec6854b545bab076dffda486ebe.
2017-02-04 05:40:22 +08:00
---
title: "Routing and Action Selection in ASP.NET Web API | Microsoft Docs"
author: MikeWasson
description: ""
ms.author: aspnetcontent
manager: wpickett
ms.date: 07/27/2012
ms.topic: article
ms.assetid: bcf2d223-cb7f-411e-be05-f43e96a14015
ms.technology: dotnet-webapi
ms.prod: .net-framework
msc.legacyurl: /web-api/overview/web-api-routing-and-actions/routing-and-action-selection
msc.type: authoredcontent
---
Routing and Action Selection in ASP.NET Web API
====================
by [Mike Wasson](https://github.com/MikeWasson)
This article describes how ASP.NET Web API routes an HTTP request to a particular action on a controller.
> [!NOTE]
> For a high-level overview of routing, see [Routing in ASP.NET Web API](routing-in-aspnet-web-api.md).
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.
Routing has three main phases:
1. 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
A route template looks similar to a URI path, but it can have placeholder values, indicated with curly braces:
[!code-powershell[Main](routing-and-action-selection/samples/sample1.ps1)]
When you create a route, you can provide default values for some or all of the placeholders:
[!code-csharp[Main](routing-and-action-selection/samples/sample2.cs)]
You can also provide constraints, which restrict how a URI segment can match a placeholder:
[!code-javascript[Main](routing-and-action-selection/samples/sample3.js)]
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.
There are two special placeholders: "{controller}" and "{action}".
- "{controller}" provides the name of the controller.
- "{action}" provides the name of the action. In Web API, the usual convention is to omit "{action}".
### Defaults
If you provide defaults, the route will match a URI that is missing those segments. For example:
[!code-csharp[Main](routing-and-action-selection/samples/sample4.cs)]
The URI "`http://localhost/api/products`" matches this route. The "{category}" segment is assigned the default value "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.
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.
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:
[!code-csharp[Main](routing-and-action-selection/samples/sample5.cs)]
For the URI path "api/products", the route dictionary will contain:
- controller: "products"
- category: "all"
For "api/products/toys/123", however, the route dictionary will contain:
- 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:
[!code-csharp[Main](routing-and-action-selection/samples/sample6.cs)]
If the URI path is "api/root/8", the dictionary will contain two values:
- 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:
1. 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. Look for a Web API controller with this type name.
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.
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".
## 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**.
The default implementation is provided by the **ApiControllerActionSelector** class. To select an action, it looks at the following:
- The HTTP method of the request.
- 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. Also, it excludes ["special name"](https://msdn.microsoft.com/en-us/library/system.reflection.methodbase.isspecialname) methods (constructors, events, operator overloads, and so forth), and methods inherited from the **ApiController** class.
**HTTP Methods.** The framework only chooses actions that match the HTTP method of the request, determined as follows:
1. You can specify the HTTP method with an attribute: **AcceptVerbs**, **HttpDelete**, **HttpGet**, **HttpHead**, **HttpOptions**, **HttpPatch**, **HttpPost**, or **HttpPut**.
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.
3. If none of the above, the method supports POST.
**Parameter Bindings.** A parameter binding is how Web API creates a value for a parameter. Here is the default rule for parameter binding:
- Simple types are taken from the URI.
- Complex types are taken from the request body.
Simple types include all of the [.NET Framework primitive types](https://msdn.microsoft.com/en-us/library/system.type.isprimitive), 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. See [WebAPI Parameter binding under the hood](https://blogs.msdn.com/b/jmstall/archive/2012/05/11/webapi-parameter-binding-under-the-hood.aspx).
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.
2. If the route dictionary has an "action" entry, remove actions whose name does not match this value.
3. Try to match action parameters to the URI, as follows:
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.
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.
3. 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. Ignore actions with the **[NonAction]** attribute.
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.
For example, consider the following action:
[!code-csharp[Main](routing-and-action-selection/samples/sample7.cs)]
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.
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.
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.
After the action is selected, all parameter bindings are invoked.
Summary:
- The action must match the HTTP method of the request.
- The action name must match the "action" entry in the route dictionary, if present.
- 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:
[!code-csharp[Main](routing-and-action-selection/samples/sample8.cs)]
Controller:
[!code-csharp[Main](routing-and-action-selection/samples/sample9.cs)]
HTTP request:
[!code-console[Main](routing-and-action-selection/samples/sample10.cmd)]
### Route Matching
The URI matches the route named "DefaultApi". The route dictionary contains the following entries:
- 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.
### Controller Selection
From the "controller" entry in the route dictionary, the controller type is `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.
Next, we try to match parameter names for the actions, looking only at the GET actions.
| 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.
The `GetAll` method matches trivially. The `GetById` method also matches, because the route dictionary contains "id". The `FindProductsByName` method does not match.
The `GetById` method wins, because it matches one parameter, versus no parameters for `GetAll`. The method is invoked with the following parameter values:
- *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.
## Extension Points
Web API provides extension points for some parts of the routing process.
| Interface | Description |
| --- | --- |
| **IHttpControllerSelector** | Selects the controller. |
| **IHttpControllerTypeResolver** | Gets the list of controller types. The **DefaultHttpControllerSelector** chooses the controller type from this list. |
| **IAssembliesResolver** | Gets the list of project assemblies. The **IHttpControllerTypeResolver** interface uses this list to find the controller types. |
| **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:
[!code-csharp[Main](routing-and-action-selection/samples/sample11.cs)]