Asp.Net Core 5 - API Versioning

Asp.Net Core 5 - API Versioning

In this article we will discuss and implement API versioning in ASP.NET Core 5

You can watch the full video on YouTube

You can the source code from GitHub using the following link: github.com/mohamadlawand087/v26-AspNetCore-..

So what we will cover today:

  • Whats the problem?
  • What is versioning?
  • Implementations
  • Different types of versioning
  • Ingredients
  • Coding

As always you will find the source code in the description down below. Please like, share and subscribe if you like the video. It will really help the channel

Problem

How are we going to be dealing with changes over time, when we are building our application we focus on completing the project without taking too much consideration about future maintainability and support for the API.

Once we publish our API we cannot change it, as systems and users will be using this API and we don't want to break their work.

What is API Versioning?

It is evolving the API without breaking the current version and the clients applications who are using it.

This is not product versioning rather on how the API functions and work.

API versioning is complex since we need to support the old version and the new version and make sure our changes doesn’t break existing functionality. We could have new clients which want to use the latest and greatest functionalities while other just want the old version with the basic implementations.

Implementations

There is a lot of ways we can use API versioning, and there is no right answer for all. Every case is different based on your user scenarios and user case.

We always need to think about our clients and how they are using this API and how we can always achieve the best results.

Versioning Types

URI

Alt Text

Query String

Alt Text

Media Type and Header

Alt Text

Ingredients

Code Time

We will start by creating a sample API, this API will basically return some information that we need.

Will start by creating our project

dotnet new webapi -n "SampleAPI"

Now let us open the project and do some clean up, we will be deleting the following

  • WeatherForecast.cs
  • Controllers/WeatherForecastController.cs

Once these 2 files are remove now its time to create a new folder in the root directory called Models, and inside the Models folder will add a new class called User.

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Now we will need to create a controller, so inside the controllers folder let us create a new class called UsersController

[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
    [HttpGet("{userId}")]
    public IActionResult GetUser(int userId)
    {
        var user = new User
        {
            Id = userId,
            Name = "Mohamad"
        };

        return Ok(user);
    }

    [HttpGet()]
    public IActionResult AllUsers()
    {
        List<User> _users = new List<User>()
        {
            new User
            {
                Id = 1,
                Name = "Mohamad"
            },
            new User
            {
                Id = 2,
                Name = "Richard"
            },
            new User
            {
                Id = 3,
                Name = "Neil"
            }
        };


        return Ok(_users);
    }
}

Now what we need to is version this API, so lets imagine after 6 months we need to add some breaking changes into our API to serve new functionality but we don't want to break any existing integrations that exist with our current APIs.

We want a way to inform the customer who is integrating with our API that is deprecated without breaking the functionality

So how can we accomplish this? there is multiple ways we can do achieve this result.

Versioning nuget package

The first way to achieve this is via Microsoft.AspNetCore.Mvc.Versioning package let us add the package to our application and see how can utilise it

dotnet add package Microsoft.AspNetCore.Mvc.Versioning --version 5.0.0

Once the package is installed we need to go to StartUp.cs and update the ConfigureServices method with the following

services.AddApiVersioning();

So now lets run our application again and see what happens, we get the following error

Why? because once we have added this line we have added the Versioning functionality to our application but we still didn't configure it and we still didn't tell the .Net core how it will handle the versioning of the API.

How can we make a work around to get access to the api, we need to add a query string to our url to inform AspNetCore which version we want

http://localhost:5000/api/users/1?api-version=1.0

We can think now that as long as we have the api-version attached to every Url we should be fine, well not exactly as we will still break current implementations with our clients we need a better way to approach this by doing the following:

We need AspNetCore to automatically default to the first version of the API to do that we need to update the ConfigureServices method in the StartupClass to the following

services.AddApiVersioning(opt =>
{
        // Will provide the different api version which is available for the client
        opt.ReportApiVersions = true;
    // this configuration will allow the api to automaticaly take api_version=1.0 in case it was not specify
    opt.AssumeDefaultVersionWhenUnspecified = true;
    // We are giving the default version of 1.0 to the api
    opt.DefaultApiVersion = ApiVersion.Default; // new ApiVersion(1, 0);
});

Now the api will be returning the api-supported version in the headers so the client will know all of the available headers

Alt Text

Now we have configured the versioning in our startup class, it's time for us to add the new API endpoints that will be replacing the old one.

So the way to do that is to organise our controllers in version folder which mean that we need to create directories under the controllers folder called v1, v2 ... and inside each of the folders we add the controllers based on what changes they have. So inside the controllers folder lets add 2 folders v1, v2 and move the UsersController to inside V1. So it will look something like this

Alt Text

And now let us update the UsersController to the following

[ApiVersion("1.0")]

and will add a .v1 to the namespace

Now let us introduce our breaking change, which will be switching the UserId from int to Guid

Inside the models folder we will add a new class called UserV2

public class UserV2
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

And inside the Controllers/v2 folder lets add a new controller called UsersController

[ApiController]
[Route("api/users")]
[ApiVersion("2.0")]
public class UsersController: ControllerBase
{
    [HttpGet("{userId}")]
    public IActionResult GetUser(Guid userId)
    {
        var user = new UserV2
        {
            Id = userId,
            Name = "Mohamad"
        };

        return Ok(user);
    }

    [HttpGet()]
    public IActionResult AllUsers()
    {
        List<UserV2> _users = new List<UserV2>()
        {
            new UserV2
            {
                Id = Guid.NewGuid(),
                Name = "Mohamad"
            },
            new UserV2
            {
                Id = Guid.NewGuid(),
                Name = "Richard"
            },
            new UserV2
            {
                Id = Guid.NewGuid(),
                Name = "Neil"
            }
        };


        return Ok(_users);
    }
}

Now having the API version number coming directly from the query string is not the right way of doing it in actual production projects, there might be a way to accomplish this in a way better way like the following

  • header
  • url segment
  • query string
  • media type

Url Segment

We have already covered the query string we will start by covering the url segment so what we want to accomplish is something like this

V1: http://localhost:5000/api/users

v2: http://localhost:5000/api/2.0/users

This is its a much cleaner way to see which api version are we targeting and to make it easier for our clients to implement. so how do we accomplish this ? We need to update our V2 UsersController to the following

[ApiController]
[Route("api/{version:apiVersion}/users")]
[ApiVersion("2.0")]

We have updated the route of our controller to automatically take the api version number, and for the v1 of the UsersController we will not change anything as we don't want to break our existing integrations

The next one is media type

We will need to update the startup class to take implement media type versioning, So inside the startup class we need to add the following

opt.ApiVersionReader = new MediaTypeApiVersionReader("x-api-version");

So now we can send the x-api-version in the accept header and we can specify which version of the API we want to use and utilise

Alt Text

Header

What if i want to have my own custom header and not use the accept header, its also very easy to do by updating the startup class to the following

opt.ApiVersionReader = new HeaderApiVersionReader("x-api-version");

And now we can send the x-api-version as its own header and our api will accept it

Alt Text

Combine multiple ways to accept api version

To enable multiple ways to accept api versions for example i want to enable the accept header and the custom header at the same time. We need to update the startup class to the following

opt.ApiVersionReader = ApiVersionReader.Combine(
    new MediaTypeApiVersionReader("x-api-version"),
    new HeaderApiVersionReader("x-api-version")
);

Alt Text

Now lets say we want to start deprecating our APIs the way to do it is to update the API version attribute as the following

[ApiVersion("1.0", Deprecated = true)]

And now our API will return the deprecated version to the client in the header like this

Alt Text

Thank you for reading, please ask your questions in the comments down below.