API Versioning

লেসন ৩৩ / ৫০

কেন Versioning?

Breaking change করতে হলে পুরনো client ভাঙবে না — নতুন version-এ deploy। সবাই ধীরে ধীরে migrate করে।

Setup

dotnet add package Asp.Versioning.Mvc
dotnet add package Asp.Versioning.Mvc.ApiExplorer

Program.cs

builder.Services.AddApiVersioning(opt => {
    opt.DefaultApiVersion = new ApiVersion(1, 0);
    opt.AssumeDefaultVersionWhenUnspecified = true;
    opt.ReportApiVersions = true;        // response header

    opt.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader(),       // /v1/
        new QueryStringApiVersionReader("api-version"),
        new HeaderApiVersionReader("X-Api-Version"),
        new MediaTypeApiVersionReader("ver")
    );
}).AddApiExplorer(opt => {
    opt.GroupNameFormat = "'v'VVV";    // v1, v2, v3
    opt.SubstituteApiVersionInUrl = true;
});

Versioned Controllers

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase {
    [HttpGet]
    public IActionResult Get() => Ok(new {
        version = "1.0",
        data = new[] { "old format" }
    });
}

[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsV2Controller : ControllerBase {
    [HttpGet]
    public IActionResult Get() => Ok(new {
        version = "2.0",
        data = new { items = new[] { "new format" }, meta = new {} }
    });
}

Same Controller, Multiple Versions

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

    [HttpGet, MapToApiVersion("1.0")]
    public IActionResult GetV1() => Ok(new { id = 1, name = "Rahim" });

    [HttpGet, MapToApiVersion("2.0")]
    public IActionResult GetV2() => Ok(new {
        id = 1,
        firstName = "Rahim",
        lastName = "Uddin"
    });
}

Deprecated Versions

[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase { ... }

// Response header:
// api-supported-versions: 2.0
// api-deprecated-versions: 1.0

Calling Different Versions

# URL segment ⭐ (recommended)
GET /api/v1/products
GET /api/v2/products

# Query string
GET /api/products?api-version=2.0

# Header
GET /api/products
X-Api-Version: 2.0

# Media type
GET /api/products
Accept: application/json;ver=2.0

Swagger Multi-version

builder.Services.AddSwaggerGen();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();

public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions> {
    private readonly IApiVersionDescriptionProvider _provider;
    public ConfigureSwaggerOptions(IApiVersionDescriptionProvider p) => _provider = p;

    public void Configure(SwaggerGenOptions opt) {
        foreach (var desc in _provider.ApiVersionDescriptions) {
            opt.SwaggerDoc(desc.GroupName, new OpenApiInfo {
                Title = $"My API {desc.ApiVersion}",
                Version = desc.ApiVersion.ToString()
            });
        }
    }
}

app.UseSwaggerUI(opt => {
    foreach (var desc in app.DescribeApiVersions()) {
        opt.SwaggerEndpoint($"/swagger/{desc.GroupName}/swagger.json",
            desc.GroupName.ToUpperInvariant());
    }
});

Versioning Strategy Tips

  • Major version বাড়ান breaking change-এ
  • Old version কমপক্ষে ৬-১২ মাস maintain
  • Deprecation header + docs-এ সতর্কতা
  • Same business logic, ভিন্ন controller — ভাল practice