Your guide to REST API versioning in ASP.NET Core
Tags: csharp, dotnet
Published: 2021-06-19 13:38:01

---
title: Your guide to REST API versioning in ASP.NET Core
published: true
date: 2021-06-16 13:39:00 UTC
tags: csharp, dotnet
canonical_url: https://www.daveabrock.com/2021/06/16/rest-api-versioning-aspnet-core/
---

_This post was originally published on the [Telerik Developer Blog.](https://www.telerik.com/blogs/your-guide-rest-api-versioning-aspnet-core)_

When you ship an API, you're inviting developers to consume it based on an agreed-upon contract. So what happens if the contract changes? Let's look at a simple example.

If I call an API with a URL of `https://mybandapi.com/api/bands/4`, I'll get the following response:

<!--kg-card-begin: markdown-->
```json
{
  "id": 4,
  "name": "The Eagles, man"
}
```
<!--kg-card-end: markdown-->

Now, let's say I decide to update my API schema with a new field, `YearFounded`. Here's how the new response looks now:

<!--kg-card-begin: markdown-->
```json
{
  "id": 4,
  "name": "The Eagles, man",
  "YearFounded": 1971
}
```
<!--kg-card-end: markdown-->

With this new field, existing clients still work. It's a non-breaking change, as existing apps can happily ignore it. You should document the new field in The Long Run, for sure, but it isn't a huge deal at the end of the day.

Let's say you want the `name` to be instead a collection of `names` associated with the band, like this:

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

You introduced a _breaking change_. Your consumers expected the `name` field to be a string value, and now you're returning a collection of strings. As a result, when consumers call the API, their applications will not work as intended, and your users are not going to Take It Easy.

Whether it's a small non-breaking change or one that breaks things, your consumers need to know what to expect. To help manage your evolving APIs, you'll need an API versioning strategy.

Since ASP.NET Core 3.1, Microsoft has [provided libraries](https://github.com/microsoft/aspnet-api-versioning) to help with API versioning. It provides a simple and powerful way to add versioning semantics to your REST services and is also compliant with the [Microsoft REST Guidelines](https://github.com/Microsoft/api-guidelines). In this post, I'll show you how you can use the `Microsoft.AspNetCore.Mvc.Versioning` NuGet package to apply API versioning to ASP.NET Core REST web APIs.

We're going to look through various approaches to versioning your APIs and how you can enable the functionality in ASP.NET Core. Which approach should you use? As always, It Depends™. There's no dogmatic "one size fits all" approach to API versioning. Instead, use the approach that works best for your situation.

## Get Started

To get started, you'll need to grab the `Microsoft.AspNetCore.Mvc.Versioning` NuGet package. The easiest way is through the .NET CLI. Execute the following command from your project's directory:

```
dotnet add package Microsoft.AspNetCore.Mvc.Versioning
```

With the package installed in your project, you'll need to add the service to ASP.NET Core's dependency injection container. From `Startup.ConfigureServices`, add the following:

```csharp
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:

```json
{
    "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
```

This isn't a great developer experience. To help this, we can introduce a default version. If consumers don't explicitly include a version in their request, we'll assume they want to use v1.0. Our library takes in an `ApiVersioningOptions`type, which we can use to specify a default version. So you're telling the consumers, "If you don't opt-in to another version, you'll be using v1.0 of our API."

In `Startup.ConfigureServices`, update your `AddApiVersioning` code to this:

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

With this in place, your consumers should be able to call a default version from `https://mybandapi.com/api/bands/4`.

## Introducing Multiple Versions for a Single Endpoint

Let's say we want to work with two versions of our API, 1.0 and 2.0. We can use this by decorating our controller with an `ApiVersionAttribute`.

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

Once I do that, I can use the `MapToApiVersionAttribute` to let ASP.NET Core know which action methods are mapped to my API versions. In my case, I've got two methods wired up to a GET on `api/bands`, `GetById` and `GetById20`.

Here's how the annotations look:

```csharp
[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)
{}
```

With this, the controller will execute the 1.0 version of the `GetById` with the normal URI (or `/api/bands/4?api-version=1.0`) and the controller executes the 2.0 version when the consumer uses `https://mybandapi.com/api/bands/4?api-version=2.0`.

Now that we're supporting multiple versions, it's a good idea to let your consumers know which versions your endpoints are supporting. To do this, you can use the following code to report API versions to the client.

```csharp
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.

![](https://www.daveabrock.com/content/images/2021/05/versions-response.jpg)

By default, the library supports versioning from query strings. I like this method. Your clients can opt-in to new versions when they're ready. And if no version is specified, consumers can rely on the default version.

Of course, this is not the only way to version an API. Aside from query strings, we'll look at other ways you can version your APIs in ASP.NET Core:

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

## Versioning with a URI Path

If I want to version with the familiar `/api/v{version number}` scheme, I can do it easily. Then, from the top of my controller, I can use a `RouteAttribute` that matches my API version. Here's how my controller annotations look now:

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

```

Then, when I call my API from `api/v2/bands/4`, I will be calling version 2 of my API. While this is a popular method and is easy to set up, it doesn't come without drawbacks. It doesn't imply a default version, so clients might feel forced to update the URIs throughout their apps whenever a change occurs.

Whether it's using a query string or the URI path, `Microsoft.AspNetCore.Mvc.Versioning` makes it easy to work with versioning from the URI level. Many clients might prefer to do away with URI versioning for various reasons. In these cases, you can use headers.

## Custom Request Headers

If you want to leave your URIs alone, you can have consumers pass a version from a request header. For example, if consumers want to use a non-default version of my API, I can have them pass in a `X-Api-Version` request header value.

When moving to headers, also consider that you're making API access a little more complicated. Instead of hitting a URI, clients need to hit your endpoints programmatically or from API tooling. This might not be a huge jump for your clients, but is something to consider.

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`." )

```csharp
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.

![](https://www.daveabrock.com/content/images/2021/05/versions-request-header.jpg)

## Media Versioning with Accept Headers

When a client passes a request to you, they use an `Accept` header to control the format of the request it can handle. These days, the most common `Accept` value is the media type of `application/json`. We can use versioning with our media types, too.

To enable this functionality, change `AddApiVersioning` to the following:

```csharp
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.

![](https://www.daveabrock.com/content/images/2021/05/versions-media-header.jpg)

## Combining Multiple Approaches

When working with the `Microsoft.AspNetCore.Mvc.Versioning` NuGet package, you aren't forced into using a single versioning method. For example, you might allow clients to choose between passing in a query string or a request header. The `ApiVersionReader` supports a static `Combine` method that allows you to specify multiple ways to read versions.

```csharp
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` .

## Learn More

This post only scratches the surface—there's so much more you can do with the `Microsoft.AspNetCore.Mvc.Versioning` NuGet package. For example, you can [work with OData](https://github.com/microsoft/aspnet-api-versioning/wiki/New-Services-Quick-Start#aspnet-web-api-with-odata-v40) and enable a [versioned API explorer](https://github.com/microsoft/aspnet-api-versioning/wiki/New-Services-Quick-Start#aspnet-web-api-with-odata-v40), enabling you to add versioned documentation to your services. If you don't enjoy decorating your controllers and methods with attributes, you can use the [controller conventions approach](https://github.com/microsoft/aspnet-api-versioning/wiki/Controller-Conventions).

Check out [the library's documentation for details](https://github.com/microsoft/aspnet-api-versioning/wiki/API-Version-Reader), and make sure to read up [on known limitations](https://github.com/microsoft/aspnet-api-versioning/wiki/Known-Limitations) (especially if you have advanced ASP.NET Core routing considerations).