Header Versioning in ASP.NET Core Web API

Header Versioning in ASP.NET Core Web API

In this article, I will discuss how to Implement Web API Versioning using Custom Header in ASP.NET Core Web API Applications with Examples. Please read our previous article discussing ASP.NET Core Web API Versioning using URL Path.

What is Web API Header Versioning?

Header Versioning is a technique used in Web API development to manage different versions of an API by specifying the version in the HTTP request headers. This method allows API consumers (i.e., Clients) to specify the version of the Web API they wish to access by setting a custom header, such as X-API-Version, of their requests without changing the URL structure. This allows the server to route requests to the appropriate version of the Web API based on the version number included in the header.

This approach allows clients to choose a specific version of the Web API they need, enabling the server to maintain backward compatibility or introduce new features without breaking existing client functionalities.

How Does Header Versioning Works in ASP.NET Core Web API?

Let us understand how Header Versioning Works. Header Versioning works as follows:

  • Client Sends Request: When making a request to the API, the client includes the Web API’s version number in a custom HTTP header.
  • Server Processes Request: The API server reads the version information from the header and routes the request to the appropriate version of the API. For example, if a header such as X-API-Version: 2 is sent with the request, the server processes this request using the second version of the API
  • Response: The server processes the request using the logic defined for that particular version and sends back the response to the client.

In ASP.NET Core Web API, when a request is received with a version indicated in the request header, the API versioning middleware parses this header and determines which version of the controller or action to invoke. Developers can configure this behavior in the Program.cs class file by specifying that the API versioning system should use a header to read version information. The controllers and actions are then decorated with version attributes that map the request to different versions of controllers or actions.

How Do We Implement Header Versioning in ASP.NET Core Web API?

Implementing header versioning in an ASP.NET Core Web API involves several steps. These include Installing the Required NuGet Packages, Configuring Header Versioning Configuration in Program.cs class file, modifying controllers to support multiple versions, and ensuring that the clients specify the version number using a Custom header in each request.

Let us proceed with the step-by-step implementation of the Header API Versioning in ASP.NET Core Web API. Create a new ASP.NET Core Web API Project named HeaderAPIVersioning.

Install Necessary Packages

To implement Header Versioning in ASP.NET Core Web API, we need to install Microsoft.AspNetCore.Mvc.Versioning package. So, first, make sure to install this package. This can be done through the NuGet Package Manager for Solution or via the Package Manager Console as follows:

Install-Package Microsoft.AspNetCore.Mvc.Versioning

Configure Header Versioning in the Program.cs Class File

In your Program.cs configure the API versioning service. This involves specifying that the API version will be read from the request header. So, modify the Program.cs class file as follows. The following code is self-explained, so please read the comment lines for a better understanding.

using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection;

namespace HeaderAPIVersioning
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            
            // Add API Versioning
            builder.Services.AddApiVersioning(options =>
            {
                // Specify the default API version
                // Default version: 1.0
                options.DefaultApiVersion = new ApiVersion(1, 0);

                // If the client does not specify a version, use the default version
                options.AssumeDefaultVersionWhenUnspecified = true;

                // Optional: to include API versions in the response headers
                options.ReportApiVersions = true;

                // Read the version number from the headers
                options.ApiVersionReader = new HeaderApiVersionReader("X-API-Version");
            });

            // Add Swagger generation
            builder.Services.AddSwaggerGen(options =>
            {
                // Defines two Swagger documents, one for API version 1.0 and another for version 2.0.
                
                // Define a Swagger document for API version 1.0
                options.SwaggerDoc("1.0", new OpenApiInfo { Title = "My API", Version = "1.0" });

                // Define a Swagger document for API version 2.0
                options.SwaggerDoc("2.0", new OpenApiInfo { Title = "My API", Version = "2.0" });

                // Resolving Conflicts
                // This resolves conflicts when multiple actions match the same route and HTTP method
                // by selecting the first action encountered
                options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());

                // Doc Inclusion Predicate
                options.DocInclusionPredicate((version, apiDesc) =>
                {
                    // Attempt to get the MethodInfo for the API Endpoint
                    if (!apiDesc.TryGetMethodInfo(out MethodInfo method))
                        return false; //If the MethodInfo for the API description cannot be retrieved, the endpoint is excluded.

                    // Extracts the versions specified by [ApiVersion] attributes at the method level.
                    var methodVersions = method.GetCustomAttributes(true)
                        .OfType<ApiVersionAttribute>()
                        .SelectMany(attr => attr.Versions);

                    // Extracts the versions specified by [ApiVersion] attributes at the controller level.
                    var controllerVersions = method.DeclaringType?
                        .GetCustomAttributes(true)
                        .OfType<ApiVersionAttribute>()
                        .SelectMany(attr => attr.Versions);

                    // Combining Versions
                    var allVersions = methodVersions.Union(controllerVersions).Distinct();

                    // Checks if any of the combined versions match the version specified in the Swagger document
                    return allVersions.Any(v => v.ToString() == version);
                });
            });

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                // Enable middleware to serve generated Swagger as a JSON endpoint
                app.UseSwagger();

                // Enable middleware to serve Swagger UI
                app.UseSwaggerUI(options =>
                {
                    // Define the Swagger endpoints for each version
                    options.SwaggerEndpoint("/swagger/1.0/swagger.json", "My API V1");
                    options.SwaggerEndpoint("/swagger/2.0/swagger.json", "My API V2");
                });
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
Define the Product Model

To demonstrate how to handle different versions of an API using Header Versioning, let’s define a product model and then use some hardcoded data to return from the action methods in different versions. So, create a class file named Product.cs within the Models folder and then copy and paste the following code.

namespace HeaderAPIVersioning.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public string Category { get; set; }
    }
}
Define API Versions in Controllers

Decorate controller actions with version attributes to specify which API versions they support. So, create the Products API Controller within the Controllers folder and copy and paste the following code. As you can see in the below code, using the [ApiVersion] attribute, we specify the version number the action method supports.

using HeaderAPIVersioning.Models;
using Microsoft.AspNetCore.Mvc;

namespace HeaderAPIVersioning.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        [HttpGet]
        [ApiVersion("1.0")]
        public IEnumerable<Product> GetV1()
        {
            // Hardcoded list of products for version 1.0
            return new List<Product>
            {
                new Product { Id = 1, Name = "Apple", Price = 1.50, Category = "Fruit" },
                new Product { Id = 2, Name = "Bread", Price = 2.25, Category = "Bakery" }
            };
        }

        [HttpGet]
        [ApiVersion("1.0")]
        [Route("{Id}")]
        public Product GetByIdV1(int Id)
        {
            return new Product { Id = Id, Name = "Apple", Price = 1.50, Category = "Fruit" };
        }

        [HttpGet]
        [ApiVersion("2.0")]
        public IEnumerable<Product> GetV2()
        {
            // Hardcoded list of products for version 2.0, including additional fields
            return new List<Product>
            {
                new Product { Id = 1, Name = "Apple", Price = 1.50, Category = "Fruit" },
                new Product { Id = 2, Name = "Bread", Price = 2.25, Category = "Bakery" },
                new Product { Id = 3, Name = "Carrot", Price = 0.75, Category = "Vegetable" }
            };
        }
    }
}
Client Request with Version Header

To access different versions of the API, clients should include the version number in the headers of their HTTP requests. For example, to request version 1.0 of the API, the client would set the following HTTP header: X-API-Version: 1.0. And similarly for version 2.0: X-API-Version: 2.0

Testing API Version 1.0:

how to Implement Web API Versioning using Custom Header in ASP.NET Core Web API

Testing API Version 2.0:

Web API Versioning using Custom Header in ASP.NET Core Web API

Applying ApiVersion Attribute at the Controller Level:

It is also possible to apply the ApiVersion Attribute at the Controller level. Let us understand this with an example. Let us create two Controllers. So, for simplicity, let us modify the Products Controller as follows. Here, you can see we have two Controllers, and each controller is decorated with the ApiVersion Attribute with a different version number.

using HeaderAPIVersioning.Models;
using Microsoft.AspNetCore.Mvc;

namespace HeaderAPIVersioning.Controllers
{
    [ApiController]
    [Route("api/products")]
    [ApiVersion("1.0")]
    public class ProductsV1Controller : ControllerBase
    {
        [HttpGet]
        public IEnumerable<Product> GetProductsV1()
        {
            // Returns a hardcoded list of products specifically for version 1.0 of the API.
            return new List<Product>
            {
                new Product { Id = 1, Name = "Apple", Price = 1.50, Category = "Fruit" },
                new Product { Id = 2, Name = "Bread", Price = 2.25, Category = "Bakery" }
            };
        }

        [HttpGet]
        [Route("{Id}")]
        public Product GetProductsByIdV1(int Id)
        {
            // Returns a hardcoded product matching the provided ID.
            return new Product { Id = Id, Name = "Apple", Price = 1.50, Category = "Fruit" }; // Returns a Product object.
        }
    }

    [ApiController]
    [Route("api/products")]
    [ApiVersion("2.0")]
    public class ProductsV2Controller : ControllerBase
    {
        [HttpGet]
        public IEnumerable<Product> GetProductsV2()
        {
            // Returns a hardcoded list of products specifically for version 2.0 of the API, with an additional product.
            return new List<Product>
            {
                new Product { Id = 1, Name = "Apple", Price = 1.50, Category = "Fruit" },
                new Product { Id = 2, Name = "Bread", Price = 2.25, Category = "Bakery" },
                new Product { Id = 3, Name = "Carrot", Price = 0.75, Category = "Vegetable" }
            };
        }
    }
}
When Should We Use Header API Versioning in Real-time Applications?

Header API versioning should be used in real-time applications when:

  • Backward Compatibility is Needed: It is essential to maintain backward compatibility for existing clients while introducing new features or changes in the API.
  • Clean URL Structure: Keeping the URL structure clean and avoiding version numbers in the URL can be beneficial for maintaining a consistent and user-friendly API endpoint structure.
  • Flexibility Routing: It provides flexibility in routing and handling requests based on different API versions without modifying the URL.
  • Iterative Development: When APIs are being developed iteratively, and multiple versions need to coexist to test new features without affecting existing functionalities.
  • Security: Header-based versioning can sometimes be more secure, as version information is not exposed in the URL.
  • Client Preference: When clients prefer or require the use of headers to specify the API version due to their architectural or operational requirements.

In the next article, I will discuss how to implement Web API Versioning using Media Type in ASP.NET Core Application. In this article, I explain how to Implement ASP.NET Core Web API Versioning using Custom HTTP Header with Examples. I hope you enjoy this ASP.NET Core Web API Versioning using Header article.

Leave a Reply

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