aspnet-api-versioning: Url Helpers not working with the api version in URL
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.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 25
@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’s123
. This is simply not the case. It’s true that123
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 pathapi/v1/order/123
andapi/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
vsapplication/xml
. One way to facilitate this would beapplication/json;v=1.0
vsapplication/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 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.