8

I'm building a web api where I have one resourse that must have 3 get methods as follows:

    [HttpGet]
    [Route("{city}/{streetName}/{streetNumber}/{littera}")]
    public IActionResult GetByAddress([FromQuery]string city, [FromQuery]string streetName, [FromQuery]int streetNumber, [FromQuery]string littera)
    {
        var model = _availabilityService.FindByAddress(city, streetName, streetNumber, littera);
        return Ok(model);
    }

    [HttpGet("{pointId}")]
    public IActionResult GetByPointId(string pointId)
    {
        var model = _availabilityService.FindByPointId(pointId);
        return Ok(model);
    }

    [HttpGet]
    [Route("{xCoordinate}/{yCoordinate}")]
    public IActionResult GetByCoordinates([FromQuery]decimal xCoordinate, [FromQuery]decimal yCoordinate)
    {
        var model = _availabilityService.FindByCoordinates(xCoordinate, yCoordinate);
        return Ok(model);
    }

The get method with only one parameter(pointId) is working fine since it is not seen as a query string but rather and id. However the remaining 2 methods are not distinguishable by the router in ASP.NET, it seems.

I'm really at a loss here and cannot figure out why it doesn't work. What I have been able to work out is that if I remove one of the methods the other one works fine.

Any suggestions on what I'm doing wrong?

FYI, the corresponding url:s ought to look like the following:

api/1.0/availabilities?city=Metropolis&streetName=Superstreet&streetNumber=1&littera=A

and

/api/1.0/availabilities?xCoordinate=34.3444&yCoordinate=66.3422

Thanks!

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Kasper P
  • 710
  • 2
  • 9
  • 14
  • When you say it's not working, what exactly do you get by calling the two other actions? A 404? – jpgrassi Aug 03 '16 at 13:09
  • @jpgrassi: I'm just getting a 500 internal server error. I've used the debugger and what happens is that the endpoint is never even reached, so my conclusion is that my routing is messed up and confused. :( – Kasper P Aug 03 '16 at 13:20

1 Answers1

20

First of all you are mixing RouteParameters and QueryParameters.

This:

[HttpGet]
[Route("{xCoordinate}/{yCoordinate}")]
public IActionResult GetByCoordinates([FromQuery]decimal xCoordinate, [FromQuery]decimal yCoordinate)
{
    var model = _availabilityService.FindByCoordinates(xCoordinate, yCoordinate);
    return Ok(model);
}

maps the controller action GetByCoordinates to a route like this:

/api/1.0/availabilities/34.3444/66.3422

But you are also specifying that you are expecting xCoordinate and yCoordinate to be bound from query parameters. So above url would match the action, but xCoordinate and yCoordinate would be bound to it's default values (in this case 0).

So to get your desired route, you shouldn't declare route parameters:

[HttpGet]
[Route("")] // <- no route parameters specified
public IActionResult GetByCoordinates([FromQuery]decimal xCoordinate, [FromQuery]decimal yCoordinate)
{
   // will be matched by e.g.
   // /api/1.0/availabilities?xCoordinate=34.3444&yCoordinate=66.3422
}

Now your desired route will match.

Note: You cannot map two actions to the same route - the route middleware wouldn't know which one to select. So also removing the route parameters from GetByAddress will effectively map both actions to the same route:

/api/1.0/availabilities?{any=number&of=query&parameters=here}

So you will have to differentiate them by another route segment for example.

[HttpGet]
[Route("address")] // <--
public IActionResult GetByAddress([FromQuery]string city, [FromQuery]string streetName, [FromQuery]int streetNumber, [FromQuery]string littera)
{
    // will be matched by e.g.
    // api/1.0/availabilities/address?city=Metropolis&streetName=Superstreet&streetNumber=1&littera=A
}

Further reading:

ModelBinding / Routing

Quick tip:

Set Microsft loglevel to Debug in appsettings.json (auto generated in standard Asp.Net Core WebApplication Template) and you will get very useful information on route selection / errors while route selection in your console output when running under kestrel.

{
  "Logging": {
  "IncludeScopes": false,
  "LogLevel": {
    "Default": "Debug",
    "System": "Information",
    "Microsoft": "Debug"
}

Or set up the debug logger in StartUp.cs to LogLevel.Debug and you get the same information in debug output directly in Visual Studio.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // ...

        loggerFactory.AddDebug(LogLevel.Debug);

        // ...
    }
ypsilo0n
  • 576
  • 5
  • 7
  • 1
    Thank you for the good explanaition.It does make sense what you are describing. This imposes a new question though, is it a conventional way of doing thins, adding a "routing-word" for a resource to specify its meaning? Thanks for the answer, I've marked it answered! :) – Kasper P Aug 04 '16 at 07:14
  • @KasperP In my opinion, do what works best for you when designing an api. That said, especialy as you are asking about convention, REST would be such an convention / paradigm you can (try) to follow. I would probably merge `GetByAddress` and `GetByCoordinates` in one search action, as it seems to be just that. [REST](http://stackoverflow.com/questions/671118/what-exactly-is-restful-programming?rq=1), [URI-Design](http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677) – ypsilo0n Aug 04 '16 at 19:37
  • Thanks for the answer. Is there any practical way to implement query flags, for a syntax such as `route/session?drop` where the parameter drop specifies that the session should be dropped if any. Or is a unique route required? – Prophet Lamb Dec 20 '21 at 23:23