Stephen Patten
Stephen Patten

Reputation: 6363

Multiple actions were found that match the request: WebAPI 2

I've read a few SO posts and none of them quite cover my scenario so I'm going to post here.

Given the following route config registration:

public static void Register(HttpConfiguration config)
{
    // Web API configuration and services

    // Web API routes
    config.MapHttpAttributeRoutes();

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

and these controller actions in a controller that inherits from ApiController:

public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{

}

public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{

}


public class GetDocumentsRequest
{
    public string CorrelationId { get; set; }
    public int Id { get; set; }
    public string ObjectId { get; set; }
    public string BusinessArea { get; set; }
    public string UserId { get; set; }
    public string SystemName { get; set; }
    public string SystemToken { get; set; }
    public Letter LetterDetails { get; set; }
    public List<KeyValuePair<string, string>> KeyValue { get; set; }
}

public class FinishDocumentsRequest
{
    public string CorrelationId { get; set; }
    public string[] Documents { get; set; }
}

I thought doing it this way would be enough disambiguation for the IHttpActionSelector to correctly choose the route, but unfortunately it is not.

So my questions is "Is there a way to make this code work correctly, and keep it in the same controller?"

Thank you, Stephen

Upvotes: 1

Views: 627

Answers (3)

Cameron
Cameron

Reputation: 2594

The request routing pipeline isn't smart enough to determine if the body of the request matches the parameter type (aka overloading). (The compiler is smart enough, which is why this compiles and you have runtime issues.)

You have a couple of different options.

  1. You can either add an [Route(<ActionName>)] attribute on both of your posts.
  2. Make two controllers, one for GetDocuments and one for FinishDocuments
  3. Make one Post method that is ambiguous. (I'd avoid this)

If you choose option 1, your API uri will have to be .../api/MyController/MyActionName rather than .../api/MyController/. It's also advisable to add [HttpGet] and [HttpPost] attributes on your methods.

Sample:

public class DocumentController : ApiController 
{
    // POST /api/Document/GetDocuments
    [HttpPost]
    [Route("GetDocuments")]
    public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request) { ... }

    // POST /api/Document/FinishDocuments
    [HttpPost]
    [Route("FinishDocuments")]
    public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request){ ...}
}

If you choose option 2, you have to maintain an additional code file.

public class GetDocumentsController : ApiController
{
    // POST /api/GetDocuments
    [HttpPost]
    public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request) { ... }
}

public class FinishDocumentsController : ApiController 
{
    // POST /api/FinishDocuments/
    [HttpPost]
    public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request){ ...}
}

If you choose option 3, may God have mercy on your soul you're going to have a bad time maintaining it.

Upvotes: 1

Marcus H&#246;glund
Marcus H&#246;glund

Reputation: 16846

You could use attribute routing for this.

Define the route as a string in the Route attribute ontop of the methods as this

[Route("api/controller/Post1")]
[HttpPost]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{

}
[Route("api/controller/Post2")]
[HttpPost]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{

}

Upvotes: 1

Avitus
Avitus

Reputation: 15968

Add the Route attribute decoration to your web api functions and that will assit the selector to choose the route:

[Route("Post1")]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{

}

[Route("Post2")]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{

}

I also recommend adding the http method decoration such as [HttpPost] or [HttpGet]

Upvotes: 0

Related Questions