1
votes

In my Web API scenario, this (default) route works well for almost all my controllers.

config.Routes.MapHttpRoute(
            name: "DefaultRoute",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

and for a couple of my controllers, I want to have them mapped by their action name, like this:

config.Routes.MapHttpRoute(
            name: "ActionRoute",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

Background:

Most of my controllers are to be mapped by default to GEE, POST, PUT and DELETE. However, I reserve some "admin" controllers that perform operations, such as data initialization, cleanup and testing stuff. These controllers may have multiple methods that are excetude via GET, hence the second route.

If I use either of these routes, they work well, however, if I set both, I get one of two failures:

Default route first:

api/books/                -> OK
api/books/someGuidId      -> OK
api/admin/someAdminAction -> 500 Internal Server Error (multiple actions found)
api/test/sometestAction   -> 500 Internal Server Error (multiple actions found)

Action route first:

api/books/                -> OK
api/books/someGuidId      -> 404 Not Found (no action that matches "someGuidId")
api/admin/someAdminAction -> OK
api/test/sometestAction   -> OK

My question is: how can I have these routes working out at the same time?

[EDIT]

api/books/someGuidId is not an important call, I can live without it (via GET) however, POST, PUT and DELETE to same url will fail, wich is not acceptable.

1
I updated my answer, do You really need "id" param in "ActionRoute"?Aleksei Chepovoi
see "Edit 2" in my answer, it will fix Your problemAleksei Chepovoi

1 Answers

3
votes

The order of the routs matter, so You should set more specific routs first:

config.Routes.MapHttpRoute(
        name: "ActionRoute",
        routeTemplate: "api/{controller}/{action}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

config.Routes.MapHttpRoute(
        name: "DefaultRoute",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

Edit:

Default route first:

api/books/

This url matchs the first pattern: api/{controller}/{id}.
Framework looks for books controller with GET (PUT,POST,DELETE) method, and finds it. This method is invoked with default id value.

api/admin/someAdminAction  

This url also matchs the first pattern. (BTW, It doesn't matter how You name Your parameters in the route definition: id or superParamName, When Framework compares URL with routs it will look only for patterns and can not distinguish by param names.

The Framework will try to invoke GET (PUT,POST,DELETE) method of admin controller with someAdminAction id (Which, I suppose, is not a valid id value:)

Similar thing You get when You define Action route first:

api/books/   

Matchs api/{controller}/{id}, id can be omitted, cause it was set to "optional".

api/books/someGuidId

Matchs api/{controller}/{action}/{id}, so, when Framework looks for someGuidId action method in book controller and it doesn't find it, it throws an exception. 404 Not Found (no action that matches "someGuidId")

Edit 2: I think, in case You will always pass id param for "Actions" and place "Actions" route first, You will not get any collisions. Try this.
If You need id param be optional in both cases, You can just remove it from both routs and pass it in a normal way via ?(question mark).

One of this would fix Your routing problem.