I want to build truly RESTful web service so don't want to leverage RPC-style, so have currently this:
[HttpGet]
[ActionName(nameof(GetByParticipant))]
public async Task<IActionResult> GetByParticipant([FromQuery]string participantId, [FromQuery]string participantType, [FromQuery]string programName)
{
}
[HttpGet]
[ActionName(nameof(GetByProgram))]
public async Task<IActionResult> GetByProgram([FromQuery]string programName)
{
}
And I believe that would work in ASP.NET Web API. But I'm getting an exception:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
TermsController.GetByParticipant (ParticipantTerms.Api)
TermsController.GetByProgram (ParticipantTerms.Api)
Neither of the attributes actually help:
[HttpGet]
[ActionName]
[FromQuery]
You can do this using an IActionConstraint.
Here is an example:
public class ExactQueryParamAttribute : Attribute, IActionConstraint
{
private readonly string[] keys;
public ExactQueryParamAttribute(params string[] keys)
{
this.keys = keys;
}
public int Order => 0;
public bool Accept(ActionConstraintContext context)
{
var query = context.RouteContext.HttpContext.Request.Query;
return query.Count == keys.Length && keys.All(key => query.ContainsKey(key));
}
}
[HttpGet]
[ActionName(nameof(GetByParticipant))]
[ExactQueryParam("participantId", "participantType", "programName")]
public async Task<IActionResult> GetByParticipant([FromQuery]string participantId, [FromQuery]string participantType, [FromQuery]string programName)
{
}
[HttpGet]
[ActionName(nameof(GetByProgram))]
[ExactQueryParam("programName")]
public async Task<IActionResult> GetByProgram([FromQuery]string programName)
{
}
When using from query you need to uniquely differentiate the actions' routes otherwise you will get the ambiguous action exception. Reason being api/action?participantId=1&participantType=2
is the same as api/action?programName=x
Suggestion:
public class ParticipantQuery {
public string participantId { get; set; }
public string participantType { get; set; }
public string programName { get; set; }
}
[Route("api/[controller]")]
public class TermsController : Controller {
[HttpGet("participants")] //GET api/terms/participants?participantId=123&....
[ActionName(nameof(GetByParticipant))]
public async Task<IActionResult> GetByParticipant([FromQuery]ParticipantQuery model) {
//...
}
[HttpGet("programs/{programName}")]//GET api/terms/programs/name
[ActionName(nameof(GetByProgram))]
public async Task<IActionResult> GetByProgram(string programName) {
//...
}
}
Or you can use one action that encapsulates the available parameters and branch the result based on the provided members
public class GetTermsQuery {
public string participantId { get; set; }
public string participantType { get; set; }
public string programName { get; set; }
}
[Route("api/[controller]")]
public class TermsController : Controller {
[HttpGet] //GET api/terms?participantId=123&....
public async Task<IActionResult> Get([FromQuery]GetTermsQuery model) {
//...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With