I have a controller class that supports operations for versions 1 and 2, as shown below:
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet, Route("testmethod")]
public string TestMethod()
{
return "Test method call successful";
}
[MapToApiVersion("2.0")]
[HttpGet, Route("addedin2")]
public string AddedIn2()
{
return "AddedIn2 ";
}
}
The operation addedin2
was introduced in version 2 as specified with the attribute MapToApiVersion
. While calling the API operation with version as 1.0 correctly returns 400 status code, but the Api-Supported-Versions still shows 1.0 and 2.0 as the supported versions. Why this is so? Is this a bug in the library, or there is something wrong with my uderstanding of usage of attribute MapToApiVersion
?
If I create a separate controller class for API version 2, the Api-Supported-Versions is returned correctly as shown below. The only problem though, the unchanged methods of version 1.0 need to be duplicated in version 2.0 which I want to avoid.
[ApiVersion("1.0")]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet, Route("testmethod")]
public string TestMethod()
{
return "Test method call successful";
}
}
[ApiVersion("2.0")]
[Route("api/test")]
[ApiController]
public class TestController2 : ControllerBase
{
[HttpGet, Route("testmethod")]
public string TestMethod()
{
return "Test method call successful";
}
[HttpGet, Route("addedin2")]
public string AddedIn2()
{
return "AddedIn2 ";
}
}
What you are observing is the correct and expected behavior. The collation of API versions is often misunderstood. While it is possible to have API versions for a specific endpoint, the intent is to version an API. An web API typically consists of more than one, related endpoint. For example, the Order API likely has at least one operation for the methods GET
, POST
, PUT
, and DELETE
. These are collectively the API and have a set of versions. When a client onboards or starts using the API, they do not align to GET /order/42
, they onboard to the entire set of available API methods.
API Versioning collates API versions by logical API. There is honestly no other reliable way. Grouping by route template is inconsistent. For example, /order/{id}
and /order/{id:int}
are semantically equivalent, but they are not the same. Now consider something like /order/{id}/items
. Is this part of the Order API or some other API? From a URL standpoint, it's unknown and an implementation detail. Grouping by logical name, be it implicit or explicit, more accurately conveys the API author's intentions. When you use controllers, API versions are collated by the name of the controller. By convention, the logical name of the API for TestController
is Test. All controllers, actions, and endpoints with this name are collated together. The reason you saw the reported versions split apart in your second definition is because the names no longer match. The default ASP.NET Core convention defines names by dropping the Controller
suffix. Since you named your controller TestController2
, the convention was broken and collated differently. Had you named the controller Test2Controller
, you would observe the same results as the original implementation. This is because API Versioning provides an additional convention which also strips off any trailing numbers. This means that TestController
and Test2Controller
will both have the name Test. There are rare cases where you might what to keep the trailing number (ex: S3Controller
). This advanced behavior can be customized via the IControllerNameConvention
service, which has several built-in implementations.
The purpose of reporting API versions is to advertise the supported and deprecated versions for an API, not a specific endpoint. There is a lot of additional information that cannot be conveyed in one or two simple HTTP headers. A supported API version does not indicate which HTTP methods are supported. A better way to support that would be to implement an OPTIONS
endpoint.
For example, a version-neural endpoint might indicate all API versions:
OPTIONS /order HTTP/2
Host: localhost
HTTP/2 200 OK
Allow: GET, POST, PUT, DELETE
Api-Supported-Versions: 1.0, 2.0
This indicates which methods and versions exist across all versions. You could optionally have a version-specific endpoint to narrow things down:
OPTIONS /order?api-version=1.0 HTTP/2
Host: localhost
HTTP/2 200 OK
Allow: GET
Api-Supported-Versions: 1.0, 2.0
This indicates which methods are allowed for a specific version, but indicates there are additional versions available.
In terms of documenting and discovering an API comprehensively, OpenAPI (formerly Swagger) is a much better option; however, reporting API versions is still useful. For example, how would a client know that they are talking to a deprecated API version? How would a client ever discover that a new API version is available? The new Asp.Versioning.Http.Client provides extensions to support these scenarios. When an interesting scenario occurs, an event is triggered for reaction. This affords the ability for a client to wire up telemetry and alerts that can indicate when something like a deprecated API version is being used. If the API supports the new sunset policy, that information is also reported. This can tell the client when a deprecated API version will be permanently sunset as well as emit links to the API's policies.