9

I have a Web API controller with the following actions:

 [HttpPut] public string Put(int id, JObject data) [HttpPut, ActionName("Lock")] public bool Lock(int id) [HttpPut, ActionName("Unlock")] public bool Unlock(int id) 

And the following routes mapped:

 routes.MapHttpRoute( name: "Api", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); routes.MapHttpRoute( name: "ApiAction", routeTemplate: "api/{controller}/{action}/{id}" ); 

When I make the following requests everything works as expected:

PUT /api/items/Lock/5 PUT /api/items/Unlock/5 

But when I attempt to make a request to:

PUT /api/items/5 

I get the following exception:

Multiple actions were found that match the request: Put(int id, JObject data) Lock(int id) Unlock(int id) 

I tried adding an empty action name to the default route but that did not help:

[HttpPut, ActionName("")] public string Put(int id, JObject data) 

Any ideas how I can combine default RESTful routing with custom action names?

EDIT: The routing mechanism is not confused by the choice of controller. It is confused by the choice of action on a single controller. What I need is to match the default action when no action is specified. Hope that clarifies things.

3
  • Looks like this might answer your Q: stackoverflow.com/questions/9499794/… Commented Oct 29, 2012 at 20:22
  • @GiscardBiamby Thanks for the link, I look it over an try it out. Commented Oct 29, 2012 at 20:31
  • @GiscardBiamby You definitely pointed me in the right direction. This answer: stackoverflow.com/a/12185249/99373 helped me figure out what I needed to do and it worked! If you want the cred, post an answer and I'll accept. Commented Oct 30, 2012 at 0:19

3 Answers 3

6

This is an expected error from the default action selector which is the ApiControllerActionSelector. You basically have three action methods which correspond to HTTP Put verb. Also keep in mind that the default action selector considers simple action parameter types which are all primitive .NET types, well-known simple types (System.String, System.DateTime, System.Decimal, System.Guid, System.DateTimeOffset, System.TimeSpan) and underlying simple types (e.g: Nullable<System.Int32>).

As a solution to your problem I would create two controllers for those as below:

public class FooController : ApiController { public string Put(int id, JObject data) } public class FooRPCController : ApiController { [HttpPut] public bool Lock(int id) [HttpPut] public bool Unlock(int id) } 

the routes would look like as below:

routes.MapHttpRoute( name: "ApiAction", routeTemplate: "api/Foo/{action}/{id}", defaults: new { controller = "FooRPC" } ); routes.MapHttpRoute( name: "Api", routeTemplate: "api/Foo/{id}", defaults: new { id = RouteParameter.Optional, controller = "Foo" } ); 

On the other hand (not completely related to your topic), I have three blog posts on action selection, especially with complex type parameters. I encourage you to check them out as they may give you a few more perspective:

Sign up to request clarification or add additional context in comments.

6 Comments

Out of curiosity, is this a case of "It can't be done that way"? I really would prefer keeping things in the same controller because it makes my application easier to maintain. There are many other controllers in the application which would need to be modified to support this proposed implementation.
@adaptive See my updated answer. If you manage to keep action selector happy, sure you can handle them in one controller.
Thanks for the insightful answer. So maybe I can write a custom ActionSelector to address the problem?
@adaptive see the update again. I added a few blog post links. They may help as well. IMHO, you should be doing any RPC but it's just my opinion. Writing an action selector from scratch is a pain (I have been there) and the default action selector is not that extendable.
Thanks for the great information. It will be a while before I can report back. Much appreciated!!
|
2

With the help of Giscard Biamby, I found this answer which pointed me in the right direction. Eventually, to solve this specific problem, I did it this way:

routes.MapHttpRoute( name: "ApiPut", routeTemplate: "api/{controller}/{id}", defaults: new { action = "Put" }, constraints: new { httpMethod = new HttpMethodConstraint("Put") } ); 

Thanks @GiscardBiamby

Comments

-3

Firstly, remove [HttpPut, ActionName("")] and then modify your route to this

config.Routes.MapHttpRoute( name: "Api", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, constraints: new { id = @"^[0-9]+$" } ); 

3 Comments

Unfortunately, I can't do that because I have other controllers with string/guid Ids. The "numeric" constraint would break those.
You can define multiple routes. Just change the above code to target your specific controller i.e. routesTemplate: "api/items/{id}", defaults: new { controller = "items"...
Please see my edits above. The routing mechanism finds the controller with no problem. The problem is that it thinks all three actions are a match for the request: PUT /api/items/5. What I need is to match the default action when no action is specified. Hope that clarifies things.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.