3

In my ASP.NET Core project I'm using aspnet-api-versioning like so:

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/users")]
[Authorize]
public class UserController : Controller
{

  [HttpGet, MapToApiVersion("1.0")]
  public async Task<IActionResult> GetUsers([FromQuery] string searchString, [FromQuery] bool allOrganizations = false)
  {
   ...
  }

}    

So the request is sent to: GET /api/v1/users.

When adding another version of this action I would just add the attribute MapToApiVersion("2.0") so it would be GET /api/v2/users. That's all fine.

However in a view I call this action with ajax like so:

$.ajax({
url: '@Url.Action("GetUsers", "User", new{version = 1})' + "?searchString=" + $("#user-search").val() + "&allOrganizations=true",
type: "GET",
success: function(data) {
response($.map(data,
function(obj) {
return {
        //some mapping
       };
 }));
}
});

This calls the endpoint:/User/GetUsers?version=1?searchString={searchString}&allOrganizations=true

So it appends the version as a query parameter instead of route parameter.

If I don't add the new {version = 1} it's the same just without the 'version' query parameter.

I've also tried using @Url.RouteUrl("Get_Users", new {version =1}) and naming the action with [HttpGet("Get_Users"), MapToApiVersion("1.0")] but that just appends the query parameters to the current url.

However if I change the Controllers route attribute from [Route("api/v{version:apiVersion}/users")] to [Route("api/users")] and ommit the version parameter, the ajax call works fine.

So is there any way to use IUrlHelper (or some other way) to get the correct route to a versioned action besides hardcoding the url string? Or am I doing versioning wrong in some way?

I may just be complicating here, but I still think this should work, no?

erikbozic
  • 1,605
  • 1
  • 16
  • 21

3 Answers3

2

I appreciate this answer is not .net core specific and may not answer you question, but consider creating api controllers with the version in the name of the controller

e.g. V1Controller, V2Controller so the Urls /api/v1/GetUsers, /api/v2/GetUsers this handles versioning and keeps the code discreet between versions and reduces any method name conflicts.

Mark Redman
  • 24,079
  • 20
  • 92
  • 147
  • Thanks for the suggestion, it makes sense. I will probably go that way if I don't find how to create a URL while using the route parameter as I'm doing now. – erikbozic Jan 18 '17 at 07:49
  • 1
    Another benefit of having separate controllers is if you have some kind of automated documentation, this will also be separated by controllers. – Mark Redman Jan 18 '17 at 07:50
2

Unfortunately, IUrlHelper is not API version-aware. I would recommend naming routes you need to resolve with a versioned name (e.g. [HttpGet(Name = "GetUsersV1")]) and then use Url.Link( "GetByUsersV1", new { version = 1 } ).

Chris Martinez
  • 3,185
  • 12
  • 28
  • Won't work... the trick is in the type passed to the version parameter. It has to be a string. Got me puzzled for a couple of days until I figured out I had tagged my API versions with a single MAJOR number. After adding a minor also I was forced to specify the argument as `"1.0"` instead `1`. So I went back and tried with `"1"` and guess what, it worked. Adding an answer to this. But thank you for this hint! – Vedran Mandić May 06 '19 at 18:59
  • 1
    Whoops. Ya' got me. Yes - it needs to be string. This is due to how the model binding metadata works; it's defined a string. This is because if you use **ApiVersion** as the model, then the default mechanism wants to break it up into constituent properties, which doesn't make sense in the context it's being used. If there are ideas to improve this, please open an issue on GitHub. I'm happy to consider it. FYI - "1" and "1.0" are semantically equivalent. You will only ever notice a difference in string formatting - meaning parsing "1" and converting back to a string will still yield "1". – Chris Martinez May 07 '19 at 17:21
2

The (got me annoyed) solution to this is to pass the exact API version value as a string value instead of an integer as you did. So in your razor snippet the IUrlHelper instance should be called like this:

url: '@Url.Action("GetUsers", "User", new { version = "1.0" })' ...

Please notice that the version is explicitly 1.0 which correctly (in terms of syntax like MAJOR.MINOR) matches the one you have specified in the attribute like [ApiVersion("1.0")].

Vedran Mandić
  • 1,084
  • 11
  • 21