Route-based API versioning for ASP.NET Core. This library works by exhaustively mapping all corresponding API versions, on startup. Only minimal APIs are currently supported.
The following packages are available,
Warning
This project is a work in progress!! The examples below will likely change as I finalise its APIs. My to-do list is:
- Minimal APIs
- OpenAPI
- Sunset header
- Tests
- NuGet package
- Controllers, if possible?
-
Define all available API versions with a
RouteVersionSetBuilder
.// In this case, the API has 3 versions. var versions = new RouteVersionSetBuilder<int>() .Version(1) .Version(2) .Version(3) .Build();
-
Use
WithVersions
to map versioned endpoints, specifying the range of versions to which they apply.var api = app.MapGroup("api").WithVersions(versions);
-
Use
From
to map an endpoint that is available from a specific version onward.// api/v1/a (introduced) // api/v2/a (unchanged) // api/v3/a (unchanged) api.From(1).MapGet("a", () => ...); // api/v2/b (introduced) // api/v3/b (unchanged) api.From(2).MapGet("b", () => ...); // api/v3/c (introduced) api.From(3).MapGet("c", () => ...);
-
Use
Between
to map an endpoint that is available in all versions within an inclusive range of versions.// api/v1/d (introduced) // api/v2/d (unchanged; retired) api.Between(1, 2).MapGet("d", () => ...);
-
Combine the two to revise an endpoint at a specific version.
// api/v1/e (introduced) // api/v2/e (unchanged) api.Between(1, 2).MapGet("e", () => ...); // api/v3/e (revised) api.From(3).MapGet("e", () => ...);
-
-
Use
Sunset
to indicate an API version being retired, which adds the specified details asSunset
andLink
headers as described in RFC 8594, to its responses.var versions = new RouteVersionSetBuilder<int>() .Version(1, (v) => v .Sunset( at: someDateTime, link: "https://example.com/changelog/v2-migration", linkMediaType: "text/html" ) ) .Build();
HTTP/1.1 200 OK Sunset: Tue, 24 Dec 2024 12:41:24 GMT Link: <https://example.com/changelog/v2-migration>; rel="sunset"; type="text/html"
-
To add a convention that applies to a specific endpoint across a range of API versions (
v*/a
), add a convention as you normally would, after theMap*
call.api.Between(1, 2).MapGet("a", () => ...).AddEndpointFilter<UwuifyFilter>(); api.From(3).MapGet("a", () => ...).AddEndpointFilter<UwuifyFilter>();
-
To add a convention that applies to all endpoints of a specific API version (
v1/*
), use the configuration delegate ofRouteVersionSetBuilder.Version
.var versions = new RouteVersionSetBuilder<int>() .Version(1, (v) => v .AddEndpointFilter<IEndpointConventionBuilder, UwuifyFilter>() ) .Build();
-
The default
Microsoft.AspNetCore.OpenApi
document includes all versioned APIs.services.AddOpenApi("current"); app.MapOpenApi(); // openapi/current.json includes: // api/v1/{a,d,e} // api/v2/{a,b,d,e} // api/v3/{a,b,c,e}
To exclude versioned operations from this document, use
ExcludeVersionedOperations()
.services.AddOpenApi("current", (options) => options .ExcludeVersionedOperations() );
-
Use
AddVersionedOpenApi
to add version-specific OpenAPI documents. To exclude unversioned endpoints from these documents, specifyincludeUnversionedEndpoints: false
.services.AddVersionedOpenApi(versions, includeUnversionedEndpoints: false); // openapi/v1.json includes api/v1/{a,d,e} // openapi/v2.json includes api/v2/{a,b,d,e} // openapi/v3.json includes api/v3/{a,b,c,e}
Use the configuration delegate of
RouteVersionSetBuilder.Version
to configure version-specific OpenAPI options.new RouteVersionSetBuilder<int>().Version(1, (v) => v .ConfigureOpenApiInfo((i) => i.Description = "v1 description") .ConfigureOpenApiOptions(...) );
-
Operations of retired API versions will be marked deprecated in version-specific OpenAPI documents. To do the same in other OpenAPI documents, use
MarkSunsettedOperations()
.services.AddOpenApi("current", (options) => options .MarkSunsettedOperations() );
-
Scalar.AspNetCore
uses the OpenAPI document specified in the path.// scalar/current // scalar/v1 // scalar/v2 // scalar/v3 app.MapScalarApiReference();
-
Swagger UI can be configured with URLs of all generated OpenAPI documents.
app.MapGet("swagger", () => { var urls = new[] { "current" } .Concat(versions.Select(versions.GetSlug)) .Select((name) => new { name, url = $"/openapi/{name}.json" }); return Results.Content(contentType: "text/html", content: $$""" <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style>body { margin: 0 }</style> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/swagger-ui.css" /> </head> <body> <div id="swagger-ui"></div> <script src="https://cdn.jsdelivr.net/npm/[email protected]/swagger-ui-bundle.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/swagger-ui-standalone-preset.js"></script> <script> SwaggerUIBundle({ urls: {{JsonSerializer.Serialize(urls)}}, dom_id: '#swagger-ui', presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset, ], layout: 'StandaloneLayout', }); </script> </body> </html> """ ); })
If you're after a library that supports versioning using query parameters, request headers, content
negotiation, etc., consider Asp.Versioning
which should be capable of everything that this library offers, with some configuration.
Comparatively, this one,
- Offers just one API versioning scheme (route-based).
- Maps all versions at startup, instead of resolving API versions as requests are made (as I understand).
- Uses API version ranges (i.e., vX-onward/between vX & Y inclusive) by default.