I am migrating controllers from .NET Framework to .NET Core and I want to be compatibility with API calls from previous version. I have problem with handling multiple routes from Query Params.
My example controller:
[Route("/api/[controller]")]
[Route("/api/[controller]/[action]")]
public class StaticFileController : ControllerBase
{
[HttpGet("{name}")]
public HttpResponseMessage GetByName(string name)
{
}
[HttpGet]
public IActionResult Get()
{
}
}
Calling api/StaticFile?name=someFunnyName will lead me to Get() action instead of expected GetByName(string name).
What I want to achieve:
api/StaticFile -> goes to Get() actionapi/StaticFile?name=someFunnyName -> goes to GetByName() actionMy app.UseEndpoints() from Startup.cs have only these lines:
endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
If I use [HttpGet] everywhere and add ([FromQuery] string name) it gets me AmbiguousMatchException: The request matched multiple endpoints
Thank you for your time to helping me (and maybe others)
What I want to achieve:
- Calling GET api/StaticFile -> goes to Get() action
- Calling GET api/StaticFile?name=someFunnyName -> goes to GetByName() action
To achieve above requirement of matching request(s) to expected action(s) based on the query string, you can try to implement a custom ActionMethodSelectorAttribute and apply it to your actions, like below.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class QueryStringConstraintAttribute : ActionMethodSelectorAttribute
{
public string QueryStingName { get; set; }
public bool CanPass { get; set; }
public QueryStringConstraintAttribute(string qname, bool canpass)
{
QueryStingName = qname;
CanPass = canpass;
}
public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
StringValues value;
routeContext.HttpContext.Request.Query.TryGetValue(QueryStingName, out value);
if (QueryStingName == "" && CanPass)
{
return true;
}
else
{
if (CanPass)
{
return !StringValues.IsNullOrEmpty(value);
}
return StringValues.IsNullOrEmpty(value);
}
}
}
Apply to Actions
[Route("api/[controller]")]
[ApiController]
public class StaticFileController : ControllerBase
{
[HttpGet]
[QueryStringConstraint("name", true)]
[QueryStringConstraint("", false)]
public IActionResult GetByName(string name)
{
return Ok("From `GetByName` Action");
}
[HttpGet]
[QueryStringConstraint("name", false)]
[QueryStringConstraint("", true)]
public IActionResult Get()
{
return Ok("From `Get` Action");
}
}
Test Result

The parameter for HttpGet sets the route, not query string parameter name.
You should add FromQuery attribute for action parameter and use HttpGet without "{name}":
[HttpGet]
public HttpResponseMessage GetByName([FromQuery] string name)
{
// ...
}
You can also set different name for query parameter:
[HttpGet]
public HttpResponseMessage GetByName([FromQuery(Name = "your_query_parameter_name")] string name)
{
// ...
}
But now you have two actions matching same route so you will get exception. The only way to execute different logic based on query string part only (the route is the same) is to check query string inside action:
[HttpGet]
public IActionResult Get([FromQuery] string name)
{
if (name == null)
{
// execute code when there is not name in query string
}
else
{
// execute code when name is in query string
}
}
So you have only one action which handles both cases using same route.
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