Aspnet-api-versioning: Add ApiVersion attribute to one Action brokes other Actions

Created on 16 Apr 2019  路  5Comments  路  Source: microsoft/aspnet-api-versioning

We have a .net core 2.1 WebAPI project with 80 controllers, 220 actions.

Want to start using versioning right now and gradually add new version Actions to exist Controllers. Old client must have access to old actions without specify api-version.

I configured:

Startup.cs:
services.AddApiVersioning(options => 
{
    options.AssumeDefaultVersionWhenUnspecified = true;                
});

All old actions still work without specifying api-version neither on controllers/actions, nor on client side, fine.

Let's look at one of our controllers:

DayMenuController.cs

[HttpGet]
public IActionResult Get() {}

[HttpPost]
public IActionResult Post([FromBody]Data data) {}

I added new, optimized Action, specifying new and old actions versions

DayMenuController.cs

[HttpGet]
[ApiVersion("1.1")]
public IActionResult GetFast() {}

[HttpGet]
[ApiVersion("1.0", Deprecated = true)]
public IActionResult Get() {}

[HttpPost]
public IActionResult Post([FromBody]Data data) {}

GetFast is called with api-version=1.1
Get is called with api-version=1.0 or without api-version, exactly what i need

But Post method (which i haven't touched at all) has broken suddenly!

POST DayMenu
The HTTP resource that matches the request URI 'http://localhost:5000/api/DayMenu' is not supported.

It seems when you specified ApiVersion at least on one of the Actions - you must specify it on all Controller's Actions as well. Option AssumeDefaultVersionWhenUnspecified is just ignored in this case.

Developer, adding new Action, must not forget to specify ApiVersion on ALL Actions (which could be dozens, in different files).

It seems very frustrating and illogical to me. How to force specify ApiVersion=1.0 on Actions without explicit version? Could you explain this?

answered asp.net core

All 5 comments

I'll look into this. What you have looks correct. There might be a bug in this particular combination.

As a workaround, the following should work just fine:

```c#
[ApiVersion("1.1")]
[ApiVersion("1.0", Deprecated = true)]
public class DayMenuController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.1")]
public IActionResult GetFast() => Ok();

[HttpGet]
public IActionResult Get() {}

[HttpPost]
public IActionResult Post([FromBody]Data data) => StatusCode(201);

}
```

With so many controllers, you might consider using _conventions_, at least for your baseline. You can mix _conventions_ and attributes. The result is a union of the two.

I've been really backed up lately, but I'll try to work on this ASAP. Thanks for your patience.

With so many controllers, you might consider using conventions,

I've tried

options.Conventions.Controller<ControllerBase>().HasApiVersion(1, 0);

(all controllers are derived from ControllerBase)

But is doesn't work.

I'm using Microsoft.AspNetCore.Mvc.Versioning (3.0.0), this is latest version supports netcore 2.1, afaik.

Inheritance is intentionally not supported. You can, however, create a custom convention with relative ease (as noted on the wiki) that will apply whatever default version you want using a convention. This is one possible way you _could_ make inheritance work if you want to. Something like:

```c#
public class MyApiConvention : IControllerConvention
{
readonly ApiVersion version;

public MyApiConvention(ApiVersion version) => this.version = version;

public bool Apply( IControllerConventionBuilder controller, ControllerModel controllerModel )
{
    controller.HasApiVersion(version);
}

}

Then you can add it to the options as:

```c#
options.Conventions.Add( new MyApiConvention( options.DefaultApiVersion ) );

I know it's been a while, but I'm finally circling back around on this. After some careful evaluation, I believe what you originally experienced is the expected behavior.

When you _grandfather_ in existing services/APIs/code, the notion of assuming the _default_ API version is to solve a scenario like yours such that you do not have to modify tens or even hunderds of touchpoints. All existing code maps to options.DefaultApiVersion. API versioning does not support a concept of no API version.

That being said, once you start making any type of modifications to an API, these rules go out the window. This is because you need to be able to sunset old APIs. Once any type of explicit mapping is done whether it be by attribute or convention, the implicit mappings are discarded. For this reason, once you start making changes, you should be holistical about applying all relevant API versions. In your case, you started interleaving new API versions beyond the default API version, which subsequently stops honoring the implicit API version convention.

If you're evolving APIs one at a time or in smaller sets, then the first solution I provided is probably the way to go. If you're trying to evolve APIs across a large set, then using a convention may be a better option (as shown in the second solution). You are also free to mix these approaches. The result is a union of all defined API versions. If you want to use interleaving (as it appears you do), then you can use a convention to easily apply API versions to all controllers and then use attributes to map API versions to specific actions.

I hope that helps. Let me know if you have more questions or whether this issue is resolved. Thanks.

It appears this issue is resolved. If you continue to have issues, feel free to come back or reopen the issue. Thanks.

Was this page helpful?
0 / 5 - 0 ratings