As your API evolves, introducing new features or making improvements may lead to breaking changes.
Breaking changes can occur in various forms, anything from modifying request/response contracts to altering the behavior of existing endpoints.
To avoid such problems, API versioning provides a structured way to manage changes while maintaining backward compatibility with existing clients.
API versioning allows you to manage multiple versions of an API, enabling you to introduce new features and improvements without affecting current clients.
With proper versioning, clients can continue using an older version of the API until they are ready to adopt the latest version.
This strategy also enables controlled deprecation of older versions, giving clients sufficient time to migrate smoothly.
Api Versioning Strategies
.NET offers several strategies to manage API versioning. Below are the most common strategies:
- URL Segment Versioning: The version number is embedded as a segment in the URL.
- Query String Versioning: The version number is provided as a query parameter in the request.
- Header Versioning: The version number is specified in a custom HTTP header.
- Media Type Versioning: The version number is part of the media type specified in the Accept header.
Getting Started
To get started with Versioning, you need to install the NuGet packages depending on your use case:
- Asp.Versioning.Http: For minimal APIs.
- Asp.Versioning.Mvc: For Controllers.
- Asp.Versioning.Mvc.ApiExplorer: For swagger.
You can do this via the NuGet Package Manager or by running the following commands in the Package Manager Console:
Install-Package Asp.Versioning.Http
Install-Package Asp.Versioning.Mvc
Install-Package Asp.Versioning.Mvc.ApiExplorer
Once packages are installed we can enable API Versioning in our application with AddApiVersioning method by defining ApiVersioningOptions.
Here is an simple example of configuring options:
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1.0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ApiVersionReader = new QueryStringApiVersionReader();
});
DefaultApiVersion specifies the default API version.
AssumeDefaultVersionWhenUnspecified when set to true, assumes the default API version if the client doesn't specify a version.
ApiVersionReader determines how the API version is read from the client request.
Depending on your versioning strategy you can choose between 4 different readers:
- UrlSegmentApiVersionReader: This reader extracts the API version from the URL path.
- HeaderApiVersionReader: This reader looks for the API version in a custom HTTP header.
- MediaTypeApiVersionReader: This reader retrieves the version from the Accept header.
- QueryStringApiVersionReader: This reader extracts the version from the query string of the request URL.
All these readers inherit from the IApiVersionReader interface, which means you can also create a custom version reader if you have specific requirements that the built-in readers don't cover.
Additionally, you can support multiple versioning strategies simultaneously by utilizing the ApiVersionReader.Combine method:
options.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader(),
new HeaderApiVersionReader("api-version"));
Minimal Api Versioning
To implement API versioning in minimal APIs, you start by creating an API version set. This version set acts as a collection of versions grouped together for easier management. Its fluent API makes configuring easy:
- HasApiVersion: Will add the API version to the set.
- ReportApiVersions: Indicates that all APIs in the version set will report their versions.
- Build: Once you define all options add this method to finilize the version set.
var versionSet = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(1.0))
.HasApiVersion(new ApiVersion(2.0))
.ReportApiVersions()
.Build();
To associate a version set with your minimal API endpoints, use the WithApiVersionSet method.
Then, use MapToApiVersion to bind specific version to each endpoint. This ensures that when a request comes in with a specific API version, the correct version of the endpoint is executed.
app.MapGet("example", () => "Example v1")
.WithApiVersionSet(versionSet)
.MapToApiVersion(1.0);
app.MapGet("example", () => "Example v2")
.WithApiVersionSet(versionSet)
.MapToApiVersion(2.0);
If your API versioning strategy is URL-based, the routes for minimal API endpoints would look like this:
app.MapGet("v{apiVersion:apiVersion}/example", () => "Example v1")
.WithApiVersionSet(versionSet)
.MapToApiVersion(1.0);
app.MapGet("v{apiVersion:apiVersion}/example", () => "Example v2")
.WithApiVersionSet(versionSet)
.MapToApiVersion(2.0);
Instead assigning a version set to each endpoint you can create route groups, which let you define the version set once and apply it to all endpoints within that group.
var group = app.MapGroup("/v{apiVersion:apiVersion}")
.WithApiVersionSet(versionSet);
group.MapGet("example", () => "Example v1")
.MapToApiVersion(1.0);
group.MapGet("example", () => "Example v2")
.MapToApiVersion(2.0);
Controller Versioning
NOTE: If you're using controllers, make sure to chain the AddApiVersioning method with the AddMvc method. This ensures that ASP.NET Core MVC is properly configured to handle API versioning.
To enable versioning in controllers, you need to decorate the controller with the ApiVersion attribute. This attribute defines which API versions the controller supports. Similarly, the MapToApiVersion attribute should be used on the endpoints to specify the exact API version they correspond to.
If your API versioning strategy is URL-based, the routes for your controller would look like this:
[ApiVersion(1.0)]
[ApiVersion(2.0)]
[ApiController]
[Route("v{apiVersion:apiVersion}/[controller]")]
public class ExampleController : ControllerBase
{
[MapToApiVersion(1.0)]
[HttpGet]
public IActionResult GetExampleV1()
{
return Ok("Example v1");
}
[MapToApiVersion(2.0)]
[HttpGet]
public IActionResult GetExampleV2()
{
return Ok("Example v2");
}
}
Deprecated API Versions
To mark an old API version as deprecated in Minimal APIs, you can use the HasDeprecatedApiVersion method while defining the version set.
var versionSet = app.NewApiVersionSet()
.HasDeprecatedApiVersion(new ApiVersion(1.0))
.HasApiVersion(new ApiVersion(2.0))
.ReportApiVersions()
.Build();
To mark an old API version as deprecated in controllers, you can use the Deprecated property on the ApiVersion attribute.
[ApiVersion(1.0, Deprecated = true)]
[ApiVersion(2.0)]
[ApiController]
[Route("v{apiVersion:apiVersion}/[controller]")]
public class ExampleController : ControllerBase
When clients access this version, they will receive a header informing them that the version is obsolete and should no longer be used.
Swagger Support
To add Swagger support for versioned APIs, you need to chain the AddApiVersioning method with the AddApiExplorer method.
AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
});
If you're using URL-based versioning, you should set SubstituteApiVersionInUrl property to true. This will replace the version placeholder in the generated JSON document endpoint URLs.
To generate documentation for each version, we can define each document using the AddSwaggerGen method and manually specify them like this:
builder.Services.AddSwaggerGen((options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "Version 1",
Title = "Controller API V1",
});
options.SwaggerDoc("v2", new OpenApiInfo
{
Version = "Version 2",
Title = "Controller API V2",
});
}));
Alternatively, for better maintainability you can dynamically configure Swagger generation options for each API version:
public class ConfigureSwaggerGenOptions(IApiVersionDescriptionProvider provider)
: IConfigureNamedOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiVersionDescriptions)
{
var openApiInfo = new OpenApiInfo
{
Title = $"Minimal API v{description.ApiVersion}",
Version = description.ApiVersion.ToString(),
};
options.SwaggerDoc(description.GroupName, openApiInfo);
}
}
public void Configure(string? name, SwaggerGenOptions options)
{
Configure(options);
}
}
This approach allows you to dynamically add versioned documentation without hardcoding each version.
Finally, update the UseSwaggerUI method to expose Swagger endpoints for each version:
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
});
}
NOTE: When working with Minimal APIs, make sure to call UseSwaggerUI after defining all of your API endpoints.
Conclusion
API versioning is essential for managing changes in your API without breaking existing client integrations. It allows you to introduce new features and improvements while maintaining backward compatibility for current users.
Additionally, deprecating old API versions is made easier by using the HasDeprecatedApiVersion method for minimal APIs or the Deprecated property on the ApiVersion attribute for controllers.
With the right approach to versioning, you can evolve your API over time without disrupting existing clients, providing them with the flexibility to choose when to adopt newer versions while still supporting legacy versions.
If you want to check out examples I created, you can find the source code here:
Source CodeI hope you enjoyed it, subscribe and get a notification when a new blog is up!