0
votes

I have the following API controller class.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;

namespace Core31Test.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class DataController : ControllerBase
    {
        [HttpGet]
        [Route("query/{idr:int?}")]
        public string Get(int idr, [FromQuery] int id)
        {
            var idx = id == 0 ? idr : id;

            return $"Value: {idx}";
        }

        [HttpGet]
        [Route("query/{cityr}")]
        public string GetByCity(string cityr, [FromQuery] string city)
        {
            var cityx = string.IsNullOrEmpty(city) ? cityr : city;

            return cityx;
        }
    }
}

When I attempt to query by an id, both the route based path and the query string work. When I attempt to query by city, only the route based path works. The querystring path ends up taking the incorrect path.

For example:

http://localhost:51123/data/query/1
Result: value: 1

http://localhost:51123/data/query?id=1
Result: value: 1

http://localhost:51123/data/query/mycity
Result: mycity

http://localhost:51123/data/query?city=acity
Result: value: 0

In the last case, the incorrect route is being selected. Why is this happening and how can I fix it?

Edit 1

If I modify the route for the GetByCity method to be the one given below, the Get method is still selected. In this case, both methods have an optional route parameter and a querystring. Since the Get route specifies the input is an integer, I do not understand why the GetByCity method is going to that one. What I would like to know is how to make this work.

[Route("query/{cityr?}")]
1
It chooses the right path, not wrong as assumed by you. The {cityr} part is not optional. So when you send http://localhost:51123/data/query?city=acity, that part {cityr} is missing and that means it cannot match the method GetByCity. It instead matches the route query/{idr:int?}, the idr is missing in the last case, the query ?city is not bound to any argument of the method Get. So both id & idr are 0.King King
@KingKing Modifying the route did not make a difference. [Route("query/{cityr?}")] still goes to Get instead of GetByCityLee Z
looks like query is not taken into consideration for matching the route and find the action method. Query values don't always require the corresponding parameters (to be bound with) because the action code can get the query values easily via query value provider (accessed like a dictionary). You should not design your apis to be in such ambiguous selection. In the last case, which method is declared first (with RouteAttribute) is more prioritized. You can try switching their declaration orders to see that.You can set Order (on [Route]) explicitly but that's not really recommended.King King

1 Answers

1
votes

In the last case, the incorrect route is being selected. Why is this happening and how can I fix it?

No, it's not. The correct and expected route is being selected. Let's take a closer look:

  • The route is query?city=acity
  • The available actions are:
    • query/ + optional int parameter (query/{idr:int?})
    • query/ + required string parameter ("query/{cityr}")

If you think about it, query/ does not satisfy query/ + required string parameter, as no value for cityr was given, so the correct route is the idr overload since that parameter is optional.