When designing a REST API, we often consider aspects such as security, caching, design patterns, API documentation, and database connectivity, among others.

However, we tend to overlook—or care less about—versioning.

In this blog, I’ll explain the importance of versioning and share some real-world use cases.

Versioning

It’s a technique that enables you to manage multiple versions of your API simultaneously.

It provides backward compatibility and maintains consistency for customers.

Versioning in ASP.NET Core Web API allows multiple versions of the same API to coexist at the same time.

It gives the flexibility to introduce changes and updates to the API without breaking the existing client applications.

There are many ways to achieve versioning, and there are NuGet packages that make our life easier in achieving versioning.

Here, we are going to explain the way of  NuGet package implementation.

Implementation

Here, I am going to create a C# based ASP.NET Core Web API in the Visual Studio 2022 IDE.

It generates the default weather forecast controller. Let’s have a look at the sections.

Controller

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{

    [HttpGet(Name = "GetWeatherForecast")]
    public IEnumerable<WeatherForecast> Get()
    {
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToArray();
    }

    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }
}

Model

public class WeatherForecast
{
    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }
}

Project Properties

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
  </ItemGroup>

</Project>

Let’s run the project. It generates API URL: https://localhost:7298/WeatherForecast

Response to the method

Let's consider this method deployed in production. The customer started to use this method, and it’s been a month.

Now, customers have a requirement to add “city” to that response.

How will you add that?

You may think of creating another controller or method. Yes, we can do that.

How many controllers will you create? Also, how will you design that in a structured way and meeting with SOLID design principles?

Don’t worry. We have versioning. Let’s go for the implementation.

Step 1 - Install NuGet package

For your note: We had a package “microsoft.aspnetcore.versioning”. but it's deprecated around August 2022.

Install packages

You can see that in the project properties after installation.

<PackageReference Include=" Asp.Versioning.Mvc" Version="8.1.0" />
<PackageReference Include=" Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />

New class model with the city

public class CityWeatherForecast
{
    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }

    public string City { get; set; }
}

Design the controller with a version mechanism

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class WeatherForecastController : ControllerBase
{
   private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet, MapToApiVersion("1.0")]
    public IEnumerable<WeatherForecast> Get()
    {
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToArray();
    }

    [HttpGet, MapToApiVersion("2.0")]
    public IEnumerable<CityWeatherForecast> GetV2()
    {
        return Enumerable.Range(1, 5).Select(index => new CityWeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)],
            City = Cities[Random.Shared.Next(Cities.Length)]

        })
        .ToArray();
    }
    private static readonly string[] Cities = new[]
    {
        "Dallas", "Boston", "Austin", "Buffalo", "Rochester", "Naperville", "Columbus", "Detroit", "Miami", "Atlanta"
    };

    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    
}

Inject API versioning in service collection in programs.cs file

// Add services to the container.
builder.Services.AddControllers();

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

Let's test through Postman URL: https://localhost:7298/api/v1/WeatherForecast

URL: https://localhost:7298/api/v2/WeatherForecast

Here you go, we have successfully designed the controller, and it works well with Postman.

Let's have a look at Swagger.

We are getting the “Failed to load API definition” error.

Yes, it will fail.

Because it treats the same path under the controller, and it doesn’t know which one to resolve to.

Now the package “Asp.Versioning. Mvc.ApiExplorer“ comes into the picture. Basically, it also allows Swagger to get all the metadata required to find all the version controllers.

Let's create a new class “ConfigureSwaggerOptions.cs” to implement “SwaggerGenOptions

public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
    private readonly IApiVersionDescriptionProvider _provider;

    public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider)
    {
        _provider = provider;
    }
       

    public void Configure(SwaggerGenOptions options)
    {
        // Add a Swagger doc for each discovered API version
        foreach (var description in _provider.ApiVersionDescriptions)
        {
            options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
        }

            
    }
    private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
    {
        var info = new OpenApiInfo
        {
            Title = "Versioned API",
            Version = description.ApiVersion.ToString(),
            Description = "A sample API with versioning and Swagger in .NET 8"
                
        };

        if (description.IsDeprecated)
        {
            info.Description += " - This API version has been deprecated.";
        }

        return info;
    }
}

We now have to inject this package into the services collection and also make a few other changes in the Program.cs file.

Update the APIVersioning section as below

builder.Services.AddApiVersioning(options=>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified=true;
    options.ReportApiVersions=true;
}).AddMvc()
.AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV"; // e.g., v1, v2
    options.SubstituteApiVersionInUrl = true;
}); ;

Inject the newly implemented class before building the app and modify the SwaggerUI section as like below.

// Apply custom SwaggerGenOptions extension (implemented below)
builder.Services.ConfigureOptions<ConfigureSwaggerOptions>();
var app = builder.Build();

app.UseSwaggerUI(options =>
{
    foreach (var description in provider.ApiVersionDescriptions)
    {
        options.SwaggerEndpoint(
            $"/swagger/{description.GroupName}/swagger.json",
            $"API {description.GroupName.ToUpperInvariant()}");
    }
}

Let’s run it.

Hurrah! Swagger is loading. Now we have flexibility in the dropdown menu to choose the version.

Summary

Here, I have covered the latest trend implementation of versioning as we are currently in .NET 8 LTS.

.NET 10 is expected to be released in November 2025 and will be designated as a Long-Term Support (LTS) version.

Hope they may not have a major change on that. I will write a new article if they have.

Hope you enjoyed the article!