How to Use API Versioning ASP.NET Core

When you publish an API, you’re inviting programmers to use it in accordance with a pre-established contract. What happens then if the contract is altered? Let’s examine a straightforward example.

I’ll get the following response if I call an API at https://mybandapi.com/api/bands/4.

{
  "id": 4,
  "name": "The Eagles, man"
}

Let’s say I decide to add a new field, YearFounded, to my API schema. The updated response now appears as follows:

{
  "id": 4,
  "name": "The Eagles, man",
  "YearFounded": 1971
}

Existing clients can continue working in this new field. Existing apps can happily ignore it, so it’s not a change that breaks anything. The new field should be covered in The Long Run, but ultimately it isn’t that important.

Suppose you prefer the name to be a combination of names connected to the band, like this:

{
  "id": 4,
  "names": 
  [
    "The Eagles, man",
    "The Eagles"
  ],
  "FoundedYear": 1971
}

You made a ground-breaking change. Consumers expected a string value in the name field, but you are currently returning a collection of strings. Because of this, when users call the API, their applications won’t function as intended, and your users won’t be able to unwind.

Your customers should be informed of any changes, no matter how minor or significant they may be. You’ll need an API versioning strategy to help you manage your changing APIs.

Microsoft has offered libraries to assist with API versioning since ASP.NET Core 3.1. It complies with the Microsoft REST Guidelines and offers a quick and effective way to add versioning semantics to your REST services. In this article, I’ll demonstrate how to apply API versioning to ASP.NET Core REST web APIs using the Microsoft.AspNetCore.Mvc.Versioning NuGet package.

We’ll examine various methods for versioning your APIs and discuss how to make ASP.NET Core functional. Which strategy ought you to employ? It DependsTM, as per usual. When it comes to API versioning, there is no rigid “one size fits all” philosophy. Use the strategy that is most effective for your circumstance instead.

Get Started

You’ll need to download the Microsoft.AspNetCore.Mvc.Versioning NuGet package to get going. The.NET CLI is the simplest method. Run the following command from the directory of your project:

dotnet add package Microsoft.AspNetCore.Mvc.Versioning

You must add the service to the dependency injection container in ASP.NET Core after the package has been installed in your project. Add the following to Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
   services.AddApiVersioning();
   // ..
}

What happens when I make a GET request on https://mybandapi.com/api/bands/4? I’m greeted with the following 400 Bad Request response:

{
      "error": {
          "code": "ApiVersionUnspecified",
          "message": "An API version is required, but was not specified.",
          "innerError": null
      }
}

By default, you need to append ?api-request=1.0 to the URL to get this working, like this:

https://mybandapi.com/api/bands/4?api-request=1.0

A bad developer experience, to be honest. We can implement a default version to aid in this. Customers will be assumed to want to use version 1.0 if they don’t specifically request a different version. We can specify a default version using the ApiVersioningOptions type that our library accepts. So, you’re telling the customers, “If you don’t opt-in to another version, you’ll be using v1.0 of our API.”

Make the following changes to your AddApiVersioning code in Startup.ConfigureServices:

services.AddApiVersioning(options =>
{
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
});

Your customers should be able to call a default version from https://mybandapi.com/api/bands/4 once this is in place.

Multiple Versions for a Single Endpoint are Introduced

Let’s assume that we want to use both the 1.0 and 2.0 versions of our API. By adding an ApiVersionAttribute to our controller, we can use this.

[Produces("application/json")]
[Route("api/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class BandsController : ControllerBase
{}

When I’ve finished, I can use the MapToApiVersionAttribute to inform ASP.NET Core of which action methods correspond to my API versions. In my case, I’ve got the GetById and GetById20 methods connected to a GET on api/bands.

Here’s how the annotations look:

[MapToApiVersion("1.0")]
[HttpGet("{id}")]
public async Task<ActionResult<Band>> GetById(int id)
{}

[MapToApiVersion("2.0")]
[HttpGet("{id}")]
public async Task<ActionResult<Band>> GetById20(int id)
{}

When the consumer uses https://mybandapi.com/api/bands/4?api-version=2.0, the controller executes the GetById 2.0 version instead of the 1.0 version with the normal URI (or /api/bands/4?api-version=1.0).

You should let your customers know which versions your endpoints support now that we support multiple versions. The following code can be used to inform the client about API versions.

services.AddApiVersioning(options =>
{
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
});

When you do this, ASP.NET Core provides an api-supported-versions response header that shows which versions an endpoint supports.

The library supports versioning from query strings by default. I favor this approach. When they’re ready, your clients can choose to subscribe to new versions. Additionally, customers can rely on the default version if no version is specified.

Of course, there are other ways to version APIs as well. We’ll examine additional techniques for versioning your APIs in ASP.NET Core besides query strings:

  • URI/URL path
  • Custom request headers
  • Media versioning with Accept headers

Versioning with a URI Path

I can easily version using the well-known /api/v {version number} scheme. Then I can use a RouteAttribute that matches my API version starting at the top of my controller. Here are my current controller annotations:

[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class BandsController : ControllerBase
{}

I will then be calling version 2 of my API when I call it from api/v2/bands/4. Despite being widely used and simple to set up, this approach has some disadvantages. Clients might feel compelled to update the URIs throughout their apps whenever a change is made because it doesn’t imply a default version.

Whether a query string or URI path is employed, Microsoft.AspNetCore.Mvc.It is simple to work with versioning at the URI level thanks to versioning. For a variety of reasons, many clients might prefer to do away with URI versioning. You can use headers in these situations.

Custom Request Headers

You can ask users to pass a request header version if you want to keep your URIs alone. For instance, I could ask users to provide an X-Api-Version request header value if they want to use a version of my API that is different from the default one.

Also take into account that switching to headers will make API access a little trickier. Clients must access your endpoints programmatically or through API tooling rather than by accessing a URI. Although it might not be a significant change for your clients, it is something to think about.

To use custom request headers, you can set the library’s ApiVersionReader to a HeaderApiVersionReader, then passing in the header name. (If you want to win Trivia Night at the pub, the answer to “What is the default ApiVersionReader?” is “QueryStringApiVersionReader.” )

services.AddApiVersioning(options =>
{
   options.DefaultApiVersion = new ApiVersion(2, 0);
   options.AssumeDefaultVersionWhenUnspecified = true;
   options.ReportApiVersions = true;
   options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version");
});

In Postman, I can pass in an X-Api-Version value to make sure it works. And it does.

Media Versioning with Accept Headers

A client uses an Accept header to specify the type of request that you can handle when sending you a request. The media type application/json is currently the most popular Accept value. Versioning is also an option for media types.

AddApiVersioning should be changed to the following value to enable this functionality:

services.AddApiVersioning(options =>
{
     options.DefaultApiVersion = new ApiVersion(1, 0);
     options.AssumeDefaultVersionWhenUnspecified = true;
     options.ReportApiVersions = true;
     options.ApiVersionReader = new MediaTypeApiVersionReader("v");
});

Clients can then pass along an API version with an Accept header, as follows.

Combining Multiple Approaches

You are not required to use a single versioning technique when utilizing the Microsoft.AspNetCore.Mvc.Versioning NuGet package. You could, for instance, give clients the option of passing either a query string or a request header. The static Combine method that the ApiVersionReader supports lets you specify different ways to read versions.

services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader =
    ApiVersionReader.Combine(
       new HeaderApiVersionReader("X-Api-Version"),
       new QueryStringApiVersionReader("version"));
});

With this in place, clients get v2.0 of our API by calling /api/bands/4?version=2 or specifying a X-Api-Version header value of 2 .

Conclusion

There is so much more you can do with the Microsoft.AspNetCore.Mvc.Versioning NuGet package than is covered in this post. To add versioned documentation to your services, for instance, you can use OData and enable a versioned API explorer. Use the controller conventions approach if you don’t like to embellish your controllers and methods with attributes.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *