How to implement API versioning with URL and Header Versioning in .NET

How to implement API versioning with URL and Header Versioning in .NET

URL and Header versioning is the most popular ways to version APIs because it is intuitive for both developers and API consumers. This method embeds the version directly in the URL or in the header, making it clear which version of the service is being consumed. This simplifies maintenance and allows different versions to coexist without conflicts.

Although there are other approaches like query string versioning, using versions in the URL is widely accepted due to its simplicity and compatibility with caching and proxies.

API versioning allows changes to be introduced without disrupting users who depend on previous versions. This facilitates system evolution without compromising service stability.

I prefer using URL versioning as it makes the versioning explicit and easy to manage. However, if I need to introduce versioning into existing APIs without changing the URL, I can switch to header versioning for greater flexibility.

How URL Versioning Works

In URL Versioning, the API version is included directly in the URL. This approach is easy to implement and intuitive for API consumers. A typical example would be:

https://api.example.com/api/v1/products
https://api.example.com/api/v2/products

This way, each version has a specific route, allowing clients to choose which version to use.

How Header Versioning Works

In header versioning, the version is passed via a custom HTTP header, such as X-API-VERSION.

https://api.example.com/api/products  -H ‘X-API-VERSION: 1’

Implementing Versioning in .NET

Let’s configure a practical example.

Step 1: Install the Required Package

First, add the packages to your project:

PowerShell
				Install-Package Asp.Versioning.Http  -> Minimal APIs
Install-Package Asp.Versioning.Mvc -> Controller APIs
Install-Package Asp.Versioning.Mvc.ApiExplorer
			

Step 2: Configure Versioning

Now, in the program.cs/startup.cs file, configure the versioning service:

C#
				services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader(),
        new QueryStringApiVersionReader("v"),
        new HeaderApiVersionReader("X-API-VERSION")
    );
}).AddMvc()
.AddApiExplorer(options =>
{
	options.GroupNameFormat = "'v'V";
	options.SubstituteApiVersionInUrl = true;
});
			

This combination provides flexibility, allowing clients to specify the version in multiple forms.

Step 3: Create Controllers or use minimal APIs for Version 1 and Version 2

Now, define separate controllers for each version:

PHP
				[ApiController]
[ApiVersion(1)]
[Route("api/v{version:apiVersion}/products")]
public class ProductsController : BaseController
{
    //...
}


[ApiController]
[ApiVersion(2)]
[Route("api/v{version:apiVersion}/users")]
public class UsersController : BaseController
{
    //...
}
			

The [ApiVersion] attribute specifies which version the controller belongs to.

  • ProductsController is available in version 1 (v1).
  • UsersController is available in version 2 (v2).

The {version:apiVersion} placeholder in the route ensures that API consumers must specify the correct version in the URL.

 

Instead of defining the API version at the controller level, you can also specify it at the method level using [MapToApiVersion]:

C#
				[HttpGet("{id}")]
[Authorize]
[MapToApiVersion(1)]
public IActionResult GetById(int id)
{
    //...
}
			

In Minimal APIs, endpoints are connected directly inside the MapGet, MapPost, etc. method without the need to use drivers like in Controllers. To add API version and routes, you can follow the below structure:

C#
				app.MapGet("/api/v{version:apiVersion}/products/{id:int}", GetProductById)
   .WithApiVersion(1)
   .Produces<Result<Product>>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);
			
C#
				ApiVersionSet apiVersionSet = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(1))
    .HasApiVersion(new ApiVersion(2))
    .ReportApiVersions()
    .Build();


app.MapGet("api/v{version:apiVersion}/users/{id}", async (Params...) =>
{
    // ...
})
.WithApiVersionSet(apiVersionSet)
.MapToApiVersion(1);
			

Deprecating API Versions:

As an API evolves, there will come a time when certain versions need to be deprecated to ensure the system remains efficient and secure. Deprecating an API version is a crucial part of API lifecycle management, allowing you to phase out outdated functionality and introduce improved, more secure features.

C#
				[ApiController]
[ApiVersion(1, Deprecated = true)]  // Set deprecated version
[ApiVersion(2)]
[Route("api/v{version:apiVersion}/users")]
public class UsersController : BaseController
			

Custom ApiVersioning Logic with Custom ApiVersionReader

Sometimes API versioning scenarios are so specific that you may need more granular control over how versions are read and managed. For these cases, CustomApiVersionReader provides a way to create your own logic to determine the API version from the request.

C#
				services.AddApiVersioning(options =>
{
    options.ApiVersionReader = new CustomApiVersionReader();
});
			

You also can easily integrate API versioning with Swagger and other tools to enhance the API documentation and ensure clients are aware of the available versions.

Conclusion

Versioning is crucial to allowing an API to evolve without breaking compatibility with existing clients. It allows changes and improvements, such as new features or bug fixes, to be made without impacting older versions. This way, users can continue to operate without issues even when new versions are released. Additionally, versioning provides control over when and how changes are made, ensuring stability and minimizing risk. In short, it ensures that the API remains scalable and easy to maintain over time.

By adopting this technique, you ensure sustainable development, allowing your API to evolve without breaking existing clients.

Have you implemented API Versioning before? What strategy do you use? Share your experience!

Thank you for reading.

See you next time!

Share the Post:
plugins premium WordPress