Question

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.

Was it helpful?

Solution 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

OTHER TIPS

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:

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]+$" }
    );  
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top