I am migrating a ASP.NET MVC (.NET Framework) web application to ASP.NET MVC Core 3.1. This application is internal to the company. We are taking the opportunity to clean up some of the API routing to make them more RESTful, for example: /api/Values?id=1
→ /api/Values/1
. However, not all of our other applications will be able to make the appropriate changes at the time when this application goes to production, so we want to be able to support both URL formats simultaneously. Is this possible? My routing setup looks like:
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.Select().Expand().Filter().OrderBy().Count().MaxTop(null);
endpoints.EnableDependencyInjection();
endpoints.MapODataRoute("ODataRoute", "odata", GetEdmModel());
});
My controller looks like this:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : Controller
{
// constructor and dependency injection omitted
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Get(int id)
{
// method logic omitted
}
}
With the above code, /api/Values/1
works fine, but the query string ?id=1
results in a 404. If I change the attribute to just [HttpGet]
then the query string works, but the RESTful version doesn't. Here's what I've tried so far:
[HttpGet("{id}")]
+ [FromQuery]
– REST: 404, QS: 405 (method not allowed)[HttpGet]
+ [FromQuery] [FromRoute]
– REST: 404, QS: 200[HttpGet("{id?}")]
only – REST: 200, QS: 404Is this possible to do? Thanks.
To achieve the requirement, you can try to define multiple routes that reach the same action, like below.
[HttpGet]
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Get(int id)
{
if (Request.Query.TryGetValue("id",out StringValues qs_id))
{
int.TryParse(qs_id.FirstOrDefault(), out id);
}
//...
// method logic omitted
//for testing purpose
return Ok($"id is {id}");
}
Test Result
Update:
If possible, you can also try to implement and use URL Rewrite Rule(s) to achieve it.
<rule name="id qs rule">
<match url="api/values" />
<conditions>
<add input="{PATH_INFO}" pattern="api/values$" />
<add input="{QUERY_STRING}" pattern="id=([0-9]+)" />
</conditions>
<action type="Rewrite" url="api/values/{C:1}/" appendQueryString="false" />
</rule>
Test Result