{"id":1714,"date":"2013-04-04T12:25:33","date_gmt":"2013-04-04T12:25:33","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/webdev\/2013\/04\/04\/debugging-asp-net-web-api-with-route-debugger\/"},"modified":"2022-08-10T00:40:46","modified_gmt":"2022-08-10T07:40:46","slug":"debugging-asp-net-web-api-with-route-debugger","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/debugging-asp-net-web-api-with-route-debugger\/","title":{"rendered":"Debugging ASP.NET Web API with Route Debugger"},"content":{"rendered":"<p>Tutorial and Tool written by Troy Dai (Twitter @troy_dai) with assistance from <a href=\"http:\/\/blogs.msdn.com\/b\/rickandy\/\">Rick Anderson<\/a> (Twitter <a href=\"https:\/\/twitter.com\/RickAndMSFT\">@RickAndMSFT<\/a>)<\/p>\n<p>Search for \u201casp.net web api routing\u201d on <a href=\"http:\/\/stackoverflow.com\/\">stackoverflow<\/a>, you\u2019ll find many questions. How exactly does Web API routing work? Why doesn\u2019t my route work? Why is this action not invoked? Often time it is difficult to debug route. <\/p>\n<p>To address this issue I wrote this tool named \u201cASP.NET Web API Route Debugger\u201d trying to make Web API developers\u2019 lives a bit easier. <\/p>\n<p>In this article I\u2019ll first introduce stdebugger. Then I\u2019ll introduce how routing works in Web Api. It is followed by three examples of how to use the route debugger in real cases.<\/p>\n<h2>How to step up the Route Debugger<\/h2>\n<p>You can install Route Debugger from NuGet (<a href=\"http:\/\/www.nuget.org\/packages\/WebApiRouteDebugger\/\">http:\/\/www.nuget.org\/packages\/WebApiRouteDebugger\/<\/a>)<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> PM&gt; Install-Package WebApiRouteDebugger<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>The NuGet package will add a new area and to your project. The image below shows the new files added to the project. (The + icon shows new files and the red check icon shows changed files)<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/5344.clip_image002_thumb_0BFC662B.jpg\"><img decoding=\"async\" title=\"clip_image002\" border=\"0\" alt=\"clip_image002\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/5344.clip_image002_thumb_0BFC662B.jpg\" width=\"291\" height=\"868\" \/><\/a><\/p>\n<p>Hit F5 to compile and then navigate to http:\/\/ localhost:xxx\/rd for the route debugger page.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/1157.clip_image004_thumb_64C21CF5.jpg\"><img decoding=\"async\" title=\"clip_image004\" border=\"0\" alt=\"clip_image004\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/1157.clip_image004_thumb_64C21CF5.jpg\" width=\"628\" height=\"183\" \/><\/a><\/p>\n<p>Enter the URL you want to test and press <b>Send<\/b>. The results page is displayed. <\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/8078.clip_image006_thumb_19627931.jpg\"><img decoding=\"async\" title=\"clip_image006\" border=\"0\" alt=\"clip_image006\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/8078.clip_image006_thumb_19627931.jpg\" width=\"628\" height=\"618\" \/><\/a><\/p>\n<p>I\u2019ll explain how to read the results in the following sections.<\/p>\n<h2>How does routing works in ASP.NET Web Api<\/h2>\n<p>The routing mechanism of ASP.NET Web API is composed of three steps: find the matching route and parse the route data, find the matching controller, and find the matching action. In any step fails to find a selection the steps following will not be executed. For example, if no controller is found, the matching ends and no action is looked for. <\/p>\n<p>In the first step, a route will be matched. Every route is defined with route template, defaults, constraints, data tokens and handler. (Routes are configured by default in App_<i>StartWebApiConfig.cs<\/i> ) Once a route is matched, the request URL is parsed into <b>route data<\/b> based on the route template. <b>Route data<\/b> is a dictionary mapping from string to object.<\/p>\n<p>Controller matching is purely done based on the value of \u201ccontroller\u201d key in route data. If the key \u201ccontroller\u201d doesn\u2019t exist in route data, controller selection will fail.<\/p>\n<p>After controller is matched, all the public methods on the controller are found through reflection. To match the action, it uses the following algorithm:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/2625.clip_image008_thumb_2B3F0CFE.png\"><img decoding=\"async\" title=\"clip_image008\" border=\"0\" alt=\"clip_image008\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/2625.clip_image008_thumb_2B3F0CFE.png\" width=\"617\" height=\"474\" \/><\/a><\/p>\n<ol>\n<li>If route data contains key \u201caction\u201d, then the action will be searched based on action name. Unlike ASP.NET MVC, Web API routes generally do not use action names in routing.<\/li>\n<ol>\n<li>Find all actions where the action name is \u201caction\u201d in the route data;<\/li>\n<li>Each action supports one or more HTTP Verbs (GET, POST, PUT, etc.). Eliminate those actions which don\u2019t support the current HTTP request\u2019s verb. <\/li>\n<\/ol>\n<li>If the route data doesn\u2019t contains key \u201caction\u201d, then the action will be searched based on the supported request method directly. <\/li>\n<li>For selected actions in either of above two steps, examine the parameters of action method. Eliminate those actions that don\u2019t match all the parameters in the route data.<\/li>\n<li>Eliminate all actions that are marked by the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.web.http.nonactionattribute_methods(v=vs.108).aspx\">NonAction<\/a> attribute.<\/li>\n<ol>\n<li>If more than one action matches, an HTTP 500 error is thrown. (Internally an HttpResponseException is thrown.)<\/li>\n<li>If there is no matching action, an HTTP 404 error is thrown. <\/li>\n<\/ol>\n<\/ol>\n<h2>How to use the Route bugger<\/h2>\n<h2>Example 1: Missing Controller Value<\/h2>\n<p>Source: <a href=\"http:\/\/stackoverflow.com\/questions\/13876816\/web-api-routing\">http:\/\/stackoverflow.com\/questions\/13876816\/web-api-routing<\/a><\/p>\n<h3>Issue<\/h3>\n<p>The controller and routes are shown below. The URL doesn\u2019t match the MachineApi route.<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> localhost\/api\/machine\/somecode\/all<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>&#160;<\/p>\n<p><em>You can download <\/em><a href=\"http:\/\/archive.msdn.microsoft.com\/Project\/Download\/FileDownload.aspx?ProjectName=aspnetmvcsamples&amp;DownloadId=16367\"><em>Sample1<\/em><\/a><em> and install the route debugger NuGet package to follow along.<\/em><\/p>\n<h3>Controller<\/h3>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> <span>public<\/span> <span>class<\/span> MachineController : ApiController<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     <span>public<\/span> IEnumerable&lt;Machine&gt; Get()<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>         <span>return<\/span> <span>new<\/span> List&lt;Machine&gt;{<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>             <span>new<\/span> Machine    {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>                 LastPlayed = DateTime.UtcNow,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>                 MachineAlertCount = 1,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>                 MachineId = <span>&quot;122&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span>                 MachineName = <span>&quot;test&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum11\">  11:<\/span>                 MachinePosition = <span>&quot;12&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum12\">  12:<\/span>                 MachineStatus = <span>&quot;test&quot;<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum13\">  13:<\/span>             }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum14\">  14:<\/span>         };<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum15\">  15:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum16\">  16:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum17\">  17:<\/span>     <span>public<\/span> IEnumerable&lt;Machine&gt; All(<span>string<\/span> code)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum18\">  18:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum19\">  19:<\/span>         <span>return<\/span> <span>new<\/span> List&lt;Machine&gt;<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum20\">  20:<\/span>         {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum21\">  21:<\/span>          <span>new<\/span> Machine<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum22\">  22:<\/span>             {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum23\">  23:<\/span>                 LastPlayed = DateTime.UtcNow,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum24\">  24:<\/span>                 MachineAlertCount = 1,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum25\">  25:<\/span>                 MachineId = <span>&quot;122&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum26\">  26:<\/span>                 MachineName = <span>&quot;test&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum27\">  27:<\/span>                 MachinePosition = <span>&quot;12&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum28\">  28:<\/span>                 MachineStatus = <span>&quot;test&quot;<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum29\">  29:<\/span>             }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum30\">  30:<\/span>         };<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum31\">  31:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum32\">  32:<\/span> }<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<h3>Route<\/h3>\n<h5>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> config.Routes.MapHttpRoute(<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span>     name: <span>&quot;MachineApi&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     routeTemplate: <span>&quot;api\/machine\/{code}\/all&quot;<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span> );<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span> config.Routes.MapHttpRoute(<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>     name: <span>&quot;DefaultApi&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>     routeTemplate: <span>&quot;api\/{controller}\/{id}&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>     defaults: <span>new<\/span> { id = RouteParameter.Optional }<\/pre>\n<p><!--CRLF--><\/div>\n<\/p><\/div>\n<\/h5>\n<h3>Test<\/h3>\n<p>Test <u>http:\/\/localhost\/api\/machine\/somecode\/all<\/u> in the route debugger:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/4452.clip_image010_thumb_520D233E.jpg\"><img decoding=\"async\" title=\"clip_image010\" border=\"0\" alt=\"clip_image010\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/4452.clip_image010_thumb_520D233E.jpg\" width=\"567\" height=\"383\" \/><\/a><\/p>\n<h3>Observation<\/h3>\n<ol>\n<li>The HTTP status code is 404 (resource not found);<\/li>\n<li>The route data contains only one key value pair, mapping \u201cSomecode\u201d to \u201cCode\u201d<\/li>\n<li>The selected route is \u201cApi\/Machine\/{Code}\/All\u201d because the template fits the URL. However there are no <u>default values<\/u> defined for this route.<\/li>\n<li>No controller matches (none of the rows are highlighted in controller selecting table)<\/li>\n<\/ol>\n<h3>Analysis<\/h3>\n<p>The route debugger output shows the \u201ccontroller\u201d value is not found in the route data or route defaults. The default controller selector relies on \u201ccontroller\u201d value to find a proper controller. <\/p>\n<p>A common misunderstanding of route templates is that the values are mapped based on their position. That\u2019s not true. In this case even though <b>Machine<\/b> is placed right after <b>Api<\/b>, there is no hint that this segment of URL should be picked up.<\/p>\n<h3>Solution<\/h3>\n<p>Add a default value specifying the machine controller to the first route:<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> config.Routes.MapHttpRoute(<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span>     name: <span>&quot;MachineApi&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     routeTemplate: <span>&quot;api\/machine\/{code}\/all&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     defaults: <span>new<\/span> { controller = <span>&quot;Machine&quot;<\/span> });<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>After this change, you get an HTTP 200 return, the machine controller is matched and the Action is matched. The matching route, controller and action are highlighted in green in the route debugger as shown below.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/5751.clip_image012_thumb_63E9B70B.png\"><img decoding=\"async\" title=\"clip_image012\" border=\"0\" alt=\"clip_image012\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/5751.clip_image012_thumb_63E9B70B.png\" width=\"628\" height=\"563\" \/><\/a><\/p>\n<h3>Similar issue:<\/h3>\n<p><a href=\"http:\/\/stackoverflow.com\/questions\/13869541\/why-is-my-message-handler-running-even-when-it-is-not-defined-in-webapi\">http:\/\/stackoverflow.com\/questions\/13869541\/why-is-my-message-handler-running-even-when-it-is-not-defined-in-webapi<\/a><\/p>\n<h2>Example 2: Ambiguous default<\/h2>\n<p>Source: <a href=\"http:\/\/stackoverflow.com\/questions\/14058228\/asp-net-web-api-no-action-was-found-on-the-controller\">http:\/\/stackoverflow.com\/questions\/14058228\/asp-net-web-api-no-action-was-found-on-the-controller<\/a><\/p>\n<h3>Controller<\/h3>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> <span>public<\/span> <span>class<\/span> ValuesController : ApiController<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     <span>\/\/ GET api\/values<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     <span>public<\/span> IEnumerable&lt;<span>string<\/span>&gt; Get()<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>         <span>return<\/span> <span>new<\/span> <span>string<\/span>[] { <span>&quot;value1&quot;<\/span>, <span>&quot;value2&quot;<\/span> };<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>     <span>\/\/ GET api\/values\/5<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>     <span>public<\/span> <span>string<\/span> Get(<span>int<\/span> id)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum11\">  11:<\/span>         <span>return<\/span> <span>&quot;value&quot;<\/span>;<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum12\">  12:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum13\">  13:<\/span>     <span>\/\/ POST api\/values<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum14\">  14:<\/span>     <span>public<\/span> <span>void<\/span> Post([FromBody]<span>string<\/span> <span>value<\/span>)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum15\">  15:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum16\">  16:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum17\">  17:<\/span>     <span>\/\/ PUT api\/values\/5<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum18\">  18:<\/span>     <span>public<\/span> <span>void<\/span> Put(<span>int<\/span> id, [FromBody]<span>string<\/span> <span>value<\/span>)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum19\">  19:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum20\">  20:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum21\">  21:<\/span>     <span>\/\/ DELETE api\/values\/5<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum22\">  22:<\/span>     <span>public<\/span> <span>void<\/span> Delete(<span>int<\/span> id)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum23\">  23:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum24\">  24:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum25\">  25:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum26\">  26:<\/span>     [HttpGet]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum27\">  27:<\/span>     <span>public<\/span> <span>void<\/span> Machines()<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum28\">  28:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum29\">  29:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum30\">  30:<\/span>     <span>public<\/span> <span>void<\/span> Machines(<span>int<\/span> id)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum31\">  31:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum32\">  32:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum33\">  33:<\/span> }<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<h3>Route definition<\/h3>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> config.Routes.MapHttpRoute(<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span>     name: <span>&quot;DefaultApi&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     routeTemplate: <span>&quot;api\/{controller}\/{action}\/{id}&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     defaults: <span>new<\/span> { action = <span>&quot;get&quot;<\/span>, id = RouteParameter.Optional });<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<h3>Test<\/h3>\n<p>Try the following three routes which work correctly<\/p>\n<ul>\n<li>\/api\/Values<\/li>\n<li>\/api\/Values\/Machines<\/li>\n<li>\/api\/Values\/Machine\/100<\/li>\n<\/ul>\n<p>However the URL \/api\/Values\/1 Returns a 404 error.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/0068.clip_image014_thumb_6A9CC08E.png\"><img decoding=\"async\" title=\"clip_image014\" border=\"0\" alt=\"clip_image014\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/0068.clip_image014_thumb_6A9CC08E.png\" width=\"628\" height=\"608\" \/><\/a><\/p>\n<h3>Observation<\/h3>\n<ol>\n<li>In the route data section you can see \u201caction\u201d is mapped to \u201c1\u201d, the third segment in the URL. It is a restriction assignment since the selected route is api\/{controller}\/{action}\/{id}<\/li>\n<li>Note that although the default mapping of \u201caction\u201d is \u201cget\u201d, the value \u201c1\u201d is assigned for the action, not the value \u201c1\u201d. <\/li>\n<li>The Values Controller is selected.<\/li>\n<li>The Action selecting table has no match. The \u201cBy Action Name\u201d column is filled with \u201cFalse\u201d, which means all actions are rejected because their action names are not matched to the \u201caction\u201d value in route data.<\/li>\n<\/ol>\n<h3>Analysis<\/h3>\n<p>There are two pivots here.<\/p>\n<ol>\n<li>The route data will always prefer the value in URL over the default value if a URL value can be found. In all of the four URLs listed above, none of them matches the default \u201caction\u201d mapping. In all four URLs, the route data contains the action key and action value.<\/li>\n<li>Because the \u201caction\u201d value exists in the route data, the action selector will pick the action from the route data.<\/li>\n<\/ol>\n<p>The route debugger tool shows that with the URL <a href=\"http:\/\/localhost:xxx\/api\/values\/1\">http:\/\/localhost:xxx\/api\/values\/1<\/a>, \u201c1\u201d is the action name and no such action exits.<\/p>\n<h3>Solution<\/h3>\n<p>Use one strategy of action matching, either by action name or by verb. Don\u2019t put both in one controller and one route. <\/p>\n<h2>Example 3: Ambiguous Action<\/h2>\n<p>Source: Why don\u2019t my routes find the appropriate action? <a href=\"http:\/\/stackoverflow.com\/questions\/14614516\/my-web-api-route-map-is-returning-multiple-actions\">http:\/\/stackoverflow.com\/questions\/14614516\/my-web-api-route-map-is-returning-multiple-actions<\/a><\/p>\n<h3>Issue<\/h3>\n<p>The URL cause 500 is http:\/\/localhost\/api\/access\/blob<\/p>\n<h3>Controller<\/h3>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> <span>public<\/span> <span>class<\/span> AccessController : ApiController<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     <span>\/\/ GET api\/access\/blob<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     [HttpGet]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>     <span>public<\/span> <span>string<\/span> Blob()<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>         <span>return<\/span> <span>&quot;blob shared access signature&quot;<\/span>;<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span>     <span>\/\/ GET api\/access\/queue<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum11\">  11:<\/span>     [HttpGet]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum12\">  12:<\/span>     <span>public<\/span> <span>string<\/span> Queue()<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum13\">  13:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum14\">  14:<\/span>         <span>return<\/span> <span>&quot;queue shared access signature&quot;<\/span>;<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum15\">  15:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum16\">  16:<\/span> }<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<h3>Route definition<\/h3>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> config.Routes.MapHttpRoute(<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span>     name: <span>&quot;DefaultApi&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     routeTemplate: <span>&quot;api\/{controller}\/{id}&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     defaults: <span>new<\/span> { id = RouteParameter.Optional }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span> );<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span> config.Routes.MapHttpRoute(<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>     name: <span>&quot;AccessApi&quot;<\/span>,<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>     routeTemplate: <span>&quot;api\/{controller}\/{action}&quot;<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span> );<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/4762.clip_image016_thumb_5853F9CC.png\"><img decoding=\"async\" title=\"clip_image016\" border=\"0\" alt=\"clip_image016\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/4762.clip_image016_thumb_5853F9CC.png\" width=\"628\" height=\"521\" \/><\/a><\/p>\n<h3>Observation<\/h3>\n<ol>\n<li>There are two routes and the URL matches both routes. The first one is selected because Web API routing selects the first route that matches (Greedy matching).<\/li>\n<li>The first route template doesn&#8217;t contain {action}, there isn\u2019t \u201caction\u201d value in route data, therefore the action will be selected based on HTTP verb<\/li>\n<li>Controller selecting successfully matches the Access controller.<\/li>\n<li>Two actions are selected using the only available matching criteria, the HTTP verb GET<\/li>\n<\/ol>\n<h3>Analysis<\/h3>\n<p>The root problem is that both routes match and the first one is picked while the developer was expecting the second route to match. The \u201caction\u201d name is ignored and eventually action selector tries to match action based on verb alone.<\/p>\n<h3>Solution<\/h3>\n<p>Two solutions:<\/p>\n<ol>\n<li>Move the default route to the end.<\/li>\n<li>Delete default route.<\/li>\n<\/ol>\n<p>The greedy route selection can lead to difficult to resolve errors, especially when you assume the wrong route was selected. The route debugger is especially useful for this problem, as it shows you the route template selected.<\/p>\n<h2>Conclusion<\/h2>\n<p>Web API routing problems can get tricky and be difficult to diagnose. The Route Debugger tool can help you find routing problems and understand how routing works. We plan to address routing problems in the future (at least partially) with <a href=\"http:\/\/aspnetwebstack.codeplex.com\/wikipage?title=Attribute%20routing%20in%20Web%20API\">Attribute routing in Web API<\/a>.<\/p>\n<h2>Source Code<\/h2>\n<p>The tool\u2019s source code is available. You can also download the source to the route debugger <a href=\"http:\/\/aspnet.codeplex.com\">http:\/\/aspnet.codeplex.com<\/a>. Click the Source Code tab and expand ToolsWebApiRouteDebugger. <\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/1122.clip_image017_thumb_032C5DDF.png\"><img decoding=\"async\" title=\"clip_image017\" border=\"0\" alt=\"clip_image017\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/04\/1122.clip_image017_thumb_032C5DDF.png\" width=\"382\" height=\"442\" \/><\/a><\/p>\n<h2>Resources<\/h2>\n<ul>\n<li><a href=\"http:\/\/www.asp.net\/web-api\/overview\/web-api-routing-and-actions\/routing-and-action-selection\">Routing and Action Selection<\/a><\/li>\n<li><a href=\"http:\/\/www.asp.net\/web-api\/overview\/web-api-routing-and-actions\/routing-in-aspnet-web-api\">Routing in ASP.NET Web API<\/a><\/li>\n<\/ul>\n<h2>Acknowledgments<\/h2>\n<ol>\n<li><a href=\"http:\/\/blogs.msdn.com\/b\/rickandy\/\">Rick Anderson<\/a> (Twitter <a href=\"https:\/\/twitter.com\/RickAndMSFT\">@RickAndMSFT<\/a>)<\/li>\n<li>Mike Wasson<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Tutorial and Tool written by Troy Dai (Twitter @troy_dai) with assistance from Rick Anderson (Twitter @RickAndMSFT) Search for \u201casp.net web api routing\u201d on stackoverflow, you\u2019ll find many questions. How exactly does Web API routing work? Why doesn\u2019t my route work? Why is this action not invoked? Often time it is difficult to debug route. To [&hellip;]<\/p>\n","protected":false},"author":415,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[197],"tags":[],"class_list":["post-1714","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aspnet"],"acf":[],"blog_post_summary":"<p>Tutorial and Tool written by Troy Dai (Twitter @troy_dai) with assistance from Rick Anderson (Twitter @RickAndMSFT) Search for \u201casp.net web api routing\u201d on stackoverflow, you\u2019ll find many questions. How exactly does Web API routing work? Why doesn\u2019t my route work? Why is this action not invoked? Often time it is difficult to debug route. To [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1714","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/415"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=1714"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1714\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=1714"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=1714"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=1714"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}