43

Closed

Both traditional and verb-based routing cannot be used in the same ApiController

description

Currently, Web Api Controllers cannot host a mixture of verb-based action methods and traditional action name routing.

For example, consider the scenario of a "business entity" controller with CRUD actions (Get/Put/Post/Delete) that also needs some additional specific functionality such as listing relationships. Using a fictional CustomerController as an example:
GET /Api/Customer/1 - Get a customer entity by id
PUT /Api/Customer/1 - Update a customer entity
GET /Api/Customer/1/Orders - list a customer's orders

To handle this scenario, I can theoretically modify my default route:
routes.MapHttpRoute(
name: "DefaultIdAction",
routeTemplate: "{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional });
Everything works as expected for the traditional routing via "named" actions (ie/ action="Orders"), but fails for the verb-based action routing (when action = RouteParameter.Optional).

The verb-based routing finds multiple matching actions for GET:
public Customer Get(int id);
[HttpGet]
public IEnumerable<Order> Orders(int id);

It appears as though the verb-based action selection code is being too "greedy".

I propose the behavior of the verb-based action selection be changed to the following:
1) Find a list of candidate actions based on the verb and method signature, as today.
2) If no candidates are found, return 404.
3) If more than one candidate is found, filter the list of candidate actions and remove any action that is declarated with an "http verb attribute". In effect, in the case where there are multiple matches only the matches with the verb prefix in their name should be included. (ie/ only "Get" and "GetCustomer", not "Get", "GetCustomer", and "[HttpGet]ListProducts")
4) If the filtered list contains only one candidate, run the action. Otherwise, throw a multiple actions found error message.

A high level description for the change could be: "If more than one potential match for a verb-based action is found, only the actions that include the verb name as a prefix in their method name will be considered."
Closed Aug 1, 2013 at 9:39 PM by eilonlipton
We recommend using the new "attribute routing" feature that is in Web API 2 (and MVC5). You can easily apply attribute routes to the action methods (including a common RoutePrefix for the controller) and customize the route on a per-action basis, as needed.

comments

ShadowChaser wrote May 25, 2012 at 8:16 PM

A related discussion of the issue with a detailed example can be found at http://forums.asp.net/p/1787921/4998070.aspx

HongmeiG wrote May 26, 2012 at 6:01 AM

We should definately make it easy in the upcoming release.

vmakhaev wrote Jun 14, 2012 at 2:54 PM

Hardly needed!

vmakhaev wrote Jun 14, 2012 at 2:56 PM

Sorry. I meant very needed!

carolynvs wrote Jun 28, 2012 at 10:49 PM

I have run into this exact same problem. I want to be able to provide named filters on top of the standard CRUD, e.g. /api/Orders/mine

Is there a workaround?

HongmeiG wrote Aug 10, 2012 at 6:10 PM

We should investigate this further to see the proposed rule won't break any existing scenarios. We should also look into making the action selector to return a list of potential matches, and have derived selector do further pruning.

kichalla wrote Mar 24, 2013 at 9:03 PM

Now that attribute routing is present in Web API, I would modify the step "3)." in the proposed solution to exclude actions which has attributes that implement IHttpRouteInfoProvider (since we know that the action has a specific route template).

tugberk wrote Mar 24, 2013 at 9:47 PM

Now that attribute routing is present in Web API
Hmm, is it now available OOTB for the future release? What do u mean exactly by that?

kstreith wrote Mar 25, 2013 at 1:15 PM

I've written a blog post that shows how both methods of routing can be used in the same controller.

http://blog.appliedis.com/2013/03/25/web-api-mixing-traditional-verb-based-routing/

MizardX wrote Apr 11, 2013 at 4:04 PM

If you made IActionMethodSelector public, you could solve this with:
public class NamedActionAttribute : Attribute, IActionMethodSelector
{
    public bool IsValidForRequest(HttpControllerContext controllerContext, MethodInfo methodInfo)
    {
        return controllerContext.RouteData.Values.ContainsKey("action");
    }
}
And decorate the action-based methods with [NamedAction]. This would make ApiControllerActionSelector only consider the method, if an action was specified.