Aspnet-api-versioning: Url Helpers not working with the api version in URL

Created on 31 Mar 2020  ·  25Comments  ·  Source: microsoft/aspnet-api-versioning

Hi,
in my API I have some problem with URL helpers with .NET core 3.1.

In startup.cs I set up the api versioning on this way:

services.AddApiVersioning(options =>
            {
                options.DefaultApiVersion = new ApiVersion(1,0);
                options.AssumeDefaultVersionWhenUnspecified = true;
                options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
                options.Conventions.Add(new VersionByNamespaceConvention());
            });

This is my controller.

    [ApiVersion("1.0")]
    [Route("api")]
    //[Route("api/v1/[controller]")]
    [ApiController]
    [Produces("application/json")]
    public class ColorController : Controller

This is my 2 actions.

        [Route("v{version:apiVersion}/[controller]/search", Name = nameof(GetColors))]
        //[Route("get", Name = nameof(GetColor))]
        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public IActionResult GetColors()

This is my post action

        [Route("v{version:apiVersion}/[controller]/add", Name = nameof(AddColor))]
        [HttpPost]
        public IActionResult AddColor([FromBody] ColoreEntity objectToAdd)

var url = Url.RouteUrl(nameof(GetColor), new {objectToAdd.idColor});
The url variable is empty.

return CreatedAtRoute(nameof(GetColor), new { idColor = objectToAdd.idColore }, objectToAdd);
this piece of code launch this exception
no route matches the supplied values

It is a bug or probably my mistake?
Thank you for your support.

answered asp.net core question

Most helpful comment

@fourpastmidnight, @candoumbe, I could go very tangential on the particulars of REST, so I'll try to keep it succinct. One of these days, I'll finally get a blog going on these topics as I've discussed them many times (here and on SO).

In terms of whether it's _my_ opinion, the answer is no. This is the opinion of Fielding, which really is the only person that can have said opinion as he is the author of REST. To say otherwise, which some have, is like a student telling their professor _"I don't think that's what the author meant."_, when the professor is the author of the book. Fielding is quite vocal on the web about this, even today. All that being said, and I'm sorry @candoumbe, PayPal is _wrong_. It's an interesting enigma as to why this style of versioning is so popular. My best _guess_ is people have never Fielding's dissertation or they think they know better.

Why is the version in the URL not RESTful you ask? Consider this: If I ask you what the _identifier_ is in api/order/123, you will almost certainly tell me that it's 123. This is simply not the case. It's true that 123 _might_ be the value in storage somewhere such as a database, but in REST parlance, the path _is_ the identifier. This leads to the API version in the path problem. If you have path api/v1/order/123 and api/v2/order/123 this implies that there are two different orders. I'd bet you a coffee the only difference between the two are the data attributes. Even if there were other differences, _logically_ they are both the same order. This method of versioning, therefore, violates the _Uniform Interface_ REST constraint. Furthermore, the change in paths also breaks your clients. This is just the tip of the iceberg. If you try to implement HATEOAS with this approach, you'll start to run into more problems unless you enforce _symmetric versioning_ (which comes with other problems).

Some people will say, _"Oh you're just being pedantic."_, but Fielding is quite clear in his definitions. REST is a system of constraints and he clearly states that you either abide by the constraints of REST or you don't. There's really not any middle ground.

How do you make a versioned API compliant with REST then? If we think about it, invariably the difference in API versions is the payload. It turns out that REST has a way to deal with this. It's called _media type negotiation_. There's a couple of ways to implement it, but it's no different than the difference between requesting application/json vs application/xml. One way to facilitate this would be application/json;v=1.0 vs application/json;v=2.0. This particular approach is supported out-of-the-box by this library. This also means that your path, or identifier (ex: api/order/123), does not change between versions. A client _asks_ for the version it wants by representation, not identifier. You might be thinking, _"Well that's all fine and good, but who implements an API like that?"_. Fair question and there's an answer. Have a look at the GitHub API. This is exactly how they version their APIs.

Now, you might be wondering, _"If all that is true, then why isn't that the default in this library?"_. There's a couple reasons, mostly due to the history of aligning to the Microsoft REST guidelines. This doesn't necessarily make them _correct_, but that's the history. The query string method tends to be a _pragmatic_ balance that doesn't violate any REST constraints. The path is always consistent and the query string doesn't identify anything. It tends to be easy for clients like JavaScript to call without having specify any headers, not even for media type.

Apologies, but this is starting to get long. Ultimately, the advocation against versioning by URL segment is from hard lessons learned in the field as well as self edification. If you have never read Fielding's dissertation, I strongly recommend it. It's quite short and you can probably read it within an hour. You can read the chapter on REST in 10-15 minutes. I'm amazed by how much of it still holds true today (20+ years later).

All 25 comments

You didn't show the action that you are trying to build a link for, but I imagine that it looks something like this:

```c#
[Route("v{version:apiVersion}/[controller]/{idColor}", Name = nameof(GetColor))]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetColor(string idColor) => Ok();


The reason `CreatedAtRoute` doesn't work is that you are versioning by URL segment and didn't provide the `apiVersion` route parameter. I would also recommend that you use `CreatedAtAction` instead of `CreatedAtRoute` because the route table is not _API Version aware_.

The following will get you what you want:

```c#
[Route("v{version:apiVersion}/[controller]/add", Name = nameof(AddColor))]
[HttpPost]
public IActionResult AddColor([FromBody] ColoreEntity objectToAdd, ApiVersion apiVersion)
{
    return CreateAtAction(
        nameof(GetColor),
        new { apiVersion = apiVersion.ToString(), idColor = objectToAdd.idColore },
        objectToAdd);
}

I hope that helps.

Hi,
thank you for your answer.
I've tried your solution but the error message throw is the same.

This is my GetColor action (sorry, I've pasted the wrong action)

        [Route("v{version:apiVersion}/[controller]/get", Name = nameof(GetColor))]
        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public IActionResult GetColor([FromQuery] int idColor)

This is my changed post action

        [Route("v{version:apiVersion}/[controller]/add", Name = nameof(AddColor))]
        [HttpPost]
        public IActionResult AddColor([FromBody] ColoreEntity objectToAdd, ApiVersion apiVersion)

       return CreatedAtAction(nameof(GetColor), new { apiVersion = apiVersion.ToString(), idColor = objectToAdd.idColore }, objectToAdd);
        }

Thank you for your support.

The changed POST should be something like:

c# [Route("v{version:apiVersion}/[controller]/add", Name = nameof(AddColor))] [HttpPost] public IActionResult AddColor([FromBody] ColoreEntity objectToAdd, ApiVersion apiVersion) { return CreateAtAction( nameof(GetColor), new { apiVersion = apiVersion.ToString() }, objectToAdd); }

It seems odd to me that you'd have a resource ID for a color where the color identifier isn't part of the path. I'm sure you have your reasons. I do not believe that the UrlHelper supports adding query string parameters, which means neither would CreatedAtRoute or CreatedAtAction. As I recall, it only fills in route parameters defined in the route template (which doesn't include query parameters).

The only way around this would be to:

  1. Call UrlHelper.Action directly
  2. Use the UriBuilder from the generated URI and then add the query string you want
  3. Set the response Location header to the generated URL
  4. Return status code 201

For reference, here's the source.

I hope that helps.

I forgotted the {idColor} parameter to my route.
Now I've changed to
[Route("v{version:apiVersion}/[controller]/get/{idColor}", Name = nameof(GetColor))]
but nothing is changed.

I'will try the solution you suggested.

You'll also have to change [FromQuery] int idColor to [FromRoute] int idColor or simply int idColor.

I've tried

var url = Url.Action("GetColor", "Color", new { apiVersion = apiVersion.ToString(), idColor = objectToAdd.idColor });
But the returned url is null;

I switch to

        [Route("v{version:apiVersion}/[controller]/getcolor/{idColor}", Name = nameof(GetColor))]
        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public IActionResult GetColor([FromRoute] int idColor)

but the error throw is the same.

sorry ... typo ... use:

c# var url = Url.Action( "GetColor", "Color", new { version = apiVersion.ToString(), idColor = objectToAdd.idColor });

The route parameter name is version, not apiVersion. Route parameters have the form: <name>[:constraint].

Fantastic! Now is working well!!!

I post the final code, if others people will have the same problem.

Controller

    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    [ApiController]
    [Produces("application/json")]
    public class ColorController : ControllerBase

Get action

    [Route("getcolor/{idColor}", Name = nameof(GetColor))]
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public IActionResult GetColor([FromRoute] int idColor)

Post action

    [Route("add", Name = nameof(AddColor))]
    [HttpPost]
   public IActionResult AddColor([FromBody] ColoreEntity objectToAdd, ApiVersion apiVersion)
   ........
   return CreatedAtAction(nameof(GetColor), new { version = apiVersion.ToString(), idColor = objectToAdd.idColore }, objectToAdd);

Really thank you for your support and for very usefull package!

Glad you got it working. ;)

@Netclick19 Thanks for posting your solution. You just saved me a boat-load of time!

@commonsensesoftware Is there some way (or, could there be some way—hint, hint, feature request 😉 ), when using URL segment-based versioning, to not to have to add an ApiVersion parameter to HTTP POST methods just so that CreatedAtAction works? If you have the following (and this is very much what I have):

[ApiVersion( "1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public sealed class MyController : Controllerbase
{
    [HttpPost]
    [Route("doSomething")
    public async Task<ActionResult> DoSomething(
        [FromBody] doSomethingDto
        /*, ApiVersion apiVersion (why???) */)
    {
        // ... Do some stuff
        return CreatedAtAction(
            nameof(GetSomething),
            new { Id = stuff.Id /*, version = apiVersion.ToString() (why???) */ },
            stuff);
    }

    [HttpGet("{id:Guid}")]
    public async Task<ActionResult> GetSomething(Guid id) { ... }
}

then API Authors would just expect that the RouteAttribute on the controller which contains {version:apiVersion} would be used by the link generators in the URL helpers just as the RouteAttribute on the controller is "inherited" by its action methods. Having to "remember" to add an extra parameter to your POST methods and in the resulting RouteParameters dictionary in URL helpers (such as CreatedAtAction) hardly helps developers fall into the pit of success. Furthermore, this is not documented anywhere except here in this issue that I luckily stumbled across. (Literally—I clicked Issues to see if _anyone_ was experiencing this—and thankfully, someone has only a few days ago, though I'm sorry to say.)

I'm agree with your suggestion. A temporary solution could be to hardcode the current apiversion and use it. Not a good solution but...

My problem with adding the apiversion parameter is with Swagger.
I use Swagger to help the users to test my api. But now 4 fields (status, major version, ...) appears as a required parameter.
there is a way to don't add this parameter to Swagger? I know that is a "Swagger problem", but maybe someone of you has a solution.

Thank you.

Couldn't we add ApiVersion as a scoped service ? so that we could inject it in controllers' constructors ?

@fourpastmidnight I believe this behavior occurs because the model binder for ApiVersion is considered a _special_ BindingSource. It can come from numerous places, including not in the URL at all. If someone knows of a way to make this a better experience, I'll entertain it. It's a minor inconvenience and yet one more reason to not version by URL segment (which is the least RESTful of all methods).

@candoumbe The ApiVersion is not a service. There's no way to inject it in a controller because it's based on the incoming request. Even if you could, I'm not sure it makes sense. The closest you could come would be an _accessor_ that can give the value upon demand, but would the the same as getting it from the request or through model binding. This would effectively been the same as behavior as IHttpContextAccessor. While technically possible, I'm not sure how much benefit this provides. The other existing methods seem to be more _natural_, but I'll entertain counter-arguments.

@commonsensesoftware
Thanks for your quick answer.

Well, IMHO think url versioning is a great way to exhibit versioned resource when doing REST as two differents version of the same "resource" have differents urls :
/path/to/the/v1/resource vs /path/to/the/v2/resource

Of course, I could version my API using header as you suggest but it seems counter intuitive when then trying to interact with it using a console app (httprepl for example).

Thanks to the fact that ApiVersion is based on the incoming request, ASP.NET Core technically allows to "simply" injects it using the services.AddScoped<TService>() right ? Plus it's exactly what's happening under the hood when adding ApiVersion as a parameter of the Post method (as suggested earlier in this thread).

I’m interested to learn more about why versioning via URI segment is “the least RESTful of all” versioning methods. I hadn’t heard this. Is this just an opinion?Are there any sources available to back up this claim? I’d love to learn more. Thanks!

Sent from my Windows 10 phone

From: Chris Martinez
Sent: Thursday, April 30, 2020 19:18
To: microsoft/aspnet-api-versioning
Cc: Craig E. Shea; Mention
Subject: Re: [microsoft/aspnet-api-versioning] Url Helpers not working withthe api version in URL (#615)

@fourpastmidnight I believe this behavior occurs because the model binder for ApiVersion is considered a special BindingSource. It can come from numerous places, including not in the URL at all. If someone knows of a way to make this a better experience, I'll entertain it. It's a minor inconvenience and yet one more reason to not version by URL segment (which is the least RESTful of all methods).
@candoumbe The ApiVersion is not a service. There's no way to inject it in a controller because it's based on the incoming request. Even if you could, I'm not sure it makes sense. The closest you could come would be an accessor that can give the value upon demand, but would the the same as getting it from the request or through model binding. This would effectively been the same as behavior as IHttpContextAccessor. While technically possible, I'm not sure how much benefit this provides. The other existing methods seem to be more natural, but I'll entertain counter-arguments.

You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

@fourpastmidnight
I modeled most of my apis after reading some articles/whitepapers.
This one from paypal is one of the most complete I came across so far

@fourpastmidnight
Here's what I came up with to be able to inject ApiVersion in the controller constructor.

services.AddScoped<ApiVersion>(sp =>
            {
                IHttpContextAccessor contextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
                IApiVersioningFeature apiVersioningFeature = contextAccessor.HttpContext.Features.Get<IApiVersioningFeature>();
                return apiVersioningFeature?.RequestedApiVersion;
            });

And then you can use it

public class MyController
{
    public MyController(ApiVersion version) // Here 'version' is now injected by the DI container and scoped to the current request 
   {
   }
}

Hope this helps

@candoumbe Yes, that looks correct. I've never tried this approach, but I'm not immediately thinking of edges where it wouldn't work. Of source if you just add ApiVersion version to any action, the model binder will provide the value there.

Would be nice to provide this out of the box. Do you want me to make a PR for this one ?
(This would be my first ever)

@fourpastmidnight, @candoumbe, I could go very tangential on the particulars of REST, so I'll try to keep it succinct. One of these days, I'll finally get a blog going on these topics as I've discussed them many times (here and on SO).

In terms of whether it's _my_ opinion, the answer is no. This is the opinion of Fielding, which really is the only person that can have said opinion as he is the author of REST. To say otherwise, which some have, is like a student telling their professor _"I don't think that's what the author meant."_, when the professor is the author of the book. Fielding is quite vocal on the web about this, even today. All that being said, and I'm sorry @candoumbe, PayPal is _wrong_. It's an interesting enigma as to why this style of versioning is so popular. My best _guess_ is people have never Fielding's dissertation or they think they know better.

Why is the version in the URL not RESTful you ask? Consider this: If I ask you what the _identifier_ is in api/order/123, you will almost certainly tell me that it's 123. This is simply not the case. It's true that 123 _might_ be the value in storage somewhere such as a database, but in REST parlance, the path _is_ the identifier. This leads to the API version in the path problem. If you have path api/v1/order/123 and api/v2/order/123 this implies that there are two different orders. I'd bet you a coffee the only difference between the two are the data attributes. Even if there were other differences, _logically_ they are both the same order. This method of versioning, therefore, violates the _Uniform Interface_ REST constraint. Furthermore, the change in paths also breaks your clients. This is just the tip of the iceberg. If you try to implement HATEOAS with this approach, you'll start to run into more problems unless you enforce _symmetric versioning_ (which comes with other problems).

Some people will say, _"Oh you're just being pedantic."_, but Fielding is quite clear in his definitions. REST is a system of constraints and he clearly states that you either abide by the constraints of REST or you don't. There's really not any middle ground.

How do you make a versioned API compliant with REST then? If we think about it, invariably the difference in API versions is the payload. It turns out that REST has a way to deal with this. It's called _media type negotiation_. There's a couple of ways to implement it, but it's no different than the difference between requesting application/json vs application/xml. One way to facilitate this would be application/json;v=1.0 vs application/json;v=2.0. This particular approach is supported out-of-the-box by this library. This also means that your path, or identifier (ex: api/order/123), does not change between versions. A client _asks_ for the version it wants by representation, not identifier. You might be thinking, _"Well that's all fine and good, but who implements an API like that?"_. Fair question and there's an answer. Have a look at the GitHub API. This is exactly how they version their APIs.

Now, you might be wondering, _"If all that is true, then why isn't that the default in this library?"_. There's a couple reasons, mostly due to the history of aligning to the Microsoft REST guidelines. This doesn't necessarily make them _correct_, but that's the history. The query string method tends to be a _pragmatic_ balance that doesn't violate any REST constraints. The path is always consistent and the query string doesn't identify anything. It tends to be easy for clients like JavaScript to call without having specify any headers, not even for media type.

Apologies, but this is starting to get long. Ultimately, the advocation against versioning by URL segment is from hard lessons learned in the field as well as self edification. If you have never read Fielding's dissertation, I strongly recommend it. It's quite short and you can probably read it within an hour. You can read the chapter on REST in 10-15 minutes. I'm amazed by how much of it still holds true today (20+ years later).

@candoumbe let me deliberate on that a bit. it should probably be tracked as a new issue that's an enhancement. If memory servers me correct, IHttpContextAccessor is not registered by default; you have to opt into it (upstream from your example). Aside from that, I don't have any huge objection, but I want to flush out any possible edge cases such as when the version is invalid or absent. This behavior should not violate the _Principle of Least Astonishment_. ;)

@commonsensesoftware: Thanks! Very helpful. I’m ashamed I haven’t read it. Now if you’ll excuse me, I have some reading to do! 😉

Sent from my Windows 10 phone

From: Chris Martinez
Sent: Thursday, April 30, 2020 21:59
To: microsoft/aspnet-api-versioning
Cc: Craig E. Shea; Mention
Subject: Re: [microsoft/aspnet-api-versioning] Url Helpers not working withthe api version in URL (#615)

@fourpastmidnight, @candoumbe, I could go very tangential on the particulars of REST, so I'll try to keep it succinct. One of these days, I'll finally get a blog going on these topics as I've discussed them many times (here and on SO).
In terms of whether it's my opinion, the answer is no. This is the opinion of Fielding, which really is the only person that can have said opinion as he is the author of REST. To say otherwise, which some have, is like a student telling their professor "I don't think that's what the author meant.", when the professor is the author of the book. Fielding is quite vocal on the web about this, even today. All that being said, and I'm sorry @candoumbe, PayPal is wrong. It's an interesting enigma as to why this style of versioning is so popular. My best guess is people have never Fielding's dissertation or they think they know better.
Why is the version in the URL not RESTful you ask? Consider this: If I ask you what the identifier is in api/order/123, you will almost certainly tell me that it's 123. This is simply not the case. It's true that 123 might be the value in storage somewhere such as a database, but in REST parlance, the path is the identifier. This leads to the API version in the path problem. If you have path api/v1/order/123 and api/v2/order/123 this implies that there are two different orders. I'd bet you a coffee the only difference between the two are the data attributes. Even if there were other differences, logically they are both the same order. This method of versioning, therefore, violates the Uniform Interface REST constraint. Furthermore, the change in paths also breaks your clients. This is just the tip of the iceberg. If you try to implement HATEOAS with this approach, you'll start to run into more problems unless you enforce symmetric versioning (which comes with other problems).
Some people will say, "Oh you're just being pedantic.", but Fielding is quite clear in his definitions. REST is a system of constraints and he clearly states that you either abide by the constraints of REST or you don't. There's really not any middle ground.
How do you make a versioned API compliant with REST then? If we think about it, invariably the difference in API versions is the payload. It turns out that REST has a way to deal with this. It's called media type negotiation. There's a couple of ways to implement it, but it's no different than the difference between requesting application/json vs application/xml. One way to facilitate this would be application/json;v=1.0 vs application/json;v=2.0. This particular approach is supported out-of-the-box by this library. This also means that your path, or identifier (ex: api/order/123), does not change between versions. A client asks for the version it wants by representation, not identifier. You might be thinking, "Well that's all fine and good, but who implements an API like that?". Fair question and there's an answer. Have a look at the GitHub API. This is exactly how they version their APIs.
Now, you might be wondering, "If all that is true, then why isn't that the default in this library?". There's a couple reasons, mostly due to the history of aligning to the Microsoft REST guidelines. This doesn't necessarily make them correct, but that's the history. The query string method tends to be a pragmatic balance that doesn't violate any REST constraints. The path is always consistent and the query string doesn't identify anything. It tends to be easy for clients like JavaScript to call without having specify any headers, not even for media type.
Apologies, but this is starting to get long. Ultimately, the advocation against versioning by URL segment is from hard lessons learned in the field as well as self edification. If you have never read Fielding's dissertation, I strongly recommend it. It's quite short and you can probably read it within an hour. You can read the chapter on REST in 10-15 minutes. I'm amazed by how much of it still holds true today (20+ years later).

You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

@commonsensesoftware
Thank you for your detailed answer. I have not been able to read the whole Fielding dissertation and as you may have guessed, english is not my mother tongue so I apologize in advance in my choice of word was not the best.

I do understand your _point of view_ and I could even agree with some things you've suggested regarding versioning in URLs vs in HTTP Header.

From what I've seen/read so far, I came across lots of differents ways of versioning and I tried to pick one. If I understand your point of view, the version number should be kept out of URI because _versioning_ has more to do with "how the resource looks like" (representation) than "what resource is the client looking for" (identification).

For me it's almost the same question that came to my mind when returning a "page" of resources from a /search endpoint. Should navigation links be part of the resources itself ?

{
    items : [
        { prop1 : val1 },
        { prop1 : val2 },
        ....,
        { prop1 : valN },

    ],
    links : {
          { relation : 'prev', href='/path/to/previous/page' },
          { relation : 'next', href='/path/to/next/page' },
          { relation : 'last', href='/path/to/last/page' },
   }
}

or should navigation links be sent in response headers ?
When looking into this, I came across the Link Header which seems to be exactly what I was looking for.

I will have a deeper look at the Fielding dissertation.
Thank you again for your answer

Regards

@candoumbe I don't really see a problem with links in responses and that would conform to HATEOAS. Where this can become problematic with URL segment versioning is how do you know when links to render? If you force _symmetric version_ (e.g. every API has the same version), then it's _feasible_. However, if you want independent APIs and clients that evolve over time, then it becomes very problematic. The server doesn't, and shouldn't, know which version the client wants. You might request order/123?api-version=2.0 with a link to customer/456. Let's assume that customer supports 1.0 and 2.0. The API version should not be included because it's up the client to ask for the representation they want. Despite calling 2.0 of order, the client want or only understand the 1.0 version. There's no easy solution to this with the version in the URL.

There are a lot of articles where you can read Fielding's stance. Here is one of many:
https://www.infoq.com/articles/roy-fielding-on-versioning/

I believe this issue has been driven to conclusion. Thanks.

Was this page helpful?
0 / 5 - 0 ratings