API-Versionierungsstrategien in .NET
# API-Versionierungsstrategien in .NET
APIs entwickeln sich weiter. Felder werden umbenannt, Endpunkte umstrukturiert, Antwortformate veraendert. Bei APIs, die von mehreren Clients konsumiert werden -- mobile Apps, Partnerintegrationen, interne Frontends -- ist Versionierung der Vertrag, der Weiterentwicklung ermoeglicht, ohne bestehende Konsumenten mitzureissen.
Warum API-Versionierung wichtig ist
Konsumenten schreiben Code gegen Ihre Antwortstrukturen, Statuscodes und Feldnamen. Aendern Sie etwas davon, brechen Sie das Versprechen. Ohne Versionierung frieren Teams entweder die API ein oder verursachen stille Brueche. Beides untergraebt Vertrauen.
Versionierungsansaetze im Vergleich
URL-Pfad-Versionierung
GET /api/v1/products
GET /api/v2/productsDer sichtbarste und verbreitetste Ansatz. Einfach zu entdecken, zu routen und zu cachen. API-Gateways koennen anhand von Pfadsegmenten routen. Nachteil: URL-Verschmutzung.
Query-String-Versionierung
GET /api/products?api-version=1.0
GET /api/products?api-version=2.0Haelt URLs sauberer und ist einfach umzusetzen, kann aber in der Dokumentation uebersehen werden. Caching-Proxys ignorieren Query-Parameter moeglicherweise standardmaessig.
Header-Versionierung
GET /api/products
X-Api-Version: 1.0Haelt die URL sauber und trennt Versionierung von Ressourcenidentifikation. Gut fuer interne APIs. Der Kompromiss ist reduzierte Entdeckbarkeit.
Media-Type-Versionierung
GET /api/products
Accept: application/vnd.myapp.v2+jsonTheoretisch der RESTful-konformste Ansatz, wird aber in der Praxis selten gewaehlt -- schwaecher im Tooling und mit zusaetzlicher kognitiver Last.
Asp.Versioning einrichten
Die `Asp.Versioning`-Bibliothek (frueher `Microsoft.AspNetCore.Mvc.Versioning`) ist der Standard fuer API-Versionierung in .NET. Installieren Sie die relevanten Pakete:
class=class="code-string">"code-comment">// Fuer Controller
dotnet add package Asp.Versioning.Mvc
dotnet add package Asp.Versioning.Mvc.ApiExplorer
class=class="code-string">"code-comment">// Fuer Minimal APIs
dotnet add package Asp.Versioning.HttpGrundkonfiguration
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(class="code-number">1, class="code-number">0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader(class="code-string">"X-Api-Version"),
new QueryStringApiVersionReader(class="code-string">"api-version")
);
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = class="code-string">"class="code-string">'v'VVV";
options.SubstituteApiVersionInUrl = true;
});`ReportApiVersions = true` fuegt jeder Antwort `api-supported-versions` und `api-deprecated-versions`-Header hinzu.
Versionierung mit Controllern
[ApiController]
[Route(class="code-string">"api/v{version:apiVersion}/[controller]")]
[ApiVersion(class="code-string">"class="code-number">1.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
return Ok(new[]
{
new { Id = class="code-number">1, Name = class="code-string">"Widget", Price = class="code-number">9.99m }
});
}
}
[ApiController]
[Route(class="code-string">"api/v{version:apiVersion}/[controller]")]
[ApiVersion(class="code-string">"class="code-number">2.0")]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
return Ok(new[]
{
new { Id = class="code-number">1, Name = class="code-string">"Widget", UnitPrice = class="code-number">9.99m, Currency = class="code-string">"USD" }
});
}
}Mehrere Versionen koennen sich einen Controller teilen:
[ApiController]
[Route(class="code-string">"api/v{version:apiVersion}/[controller]")]
[ApiVersion(class="code-string">"class="code-number">1.0")]
[ApiVersion(class="code-string">"class="code-number">2.0")]
public class CustomersController : ControllerBase
{
[HttpGet]
[MapToApiVersion(class="code-string">"class="code-number">1.0")]
public IActionResult GetV1() => Ok(new { Name = class="code-string">"Acme" });
[HttpGet]
[MapToApiVersion(class="code-string">"class="code-number">2.0")]
public IActionResult GetV2() => Ok(new { Name = class="code-string">"Acme", Tier = class="code-string">"Enterprise" });
}Versionierung mit Minimal APIs
var versionSet = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(class="code-number">1, class="code-number">0))
.HasApiVersion(new ApiVersion(class="code-number">2, class="code-number">0))
.ReportApiVersions()
.Build();
var v1 = app.MapGroup(class="code-string">"api/v{version:apiVersion}/products")
.WithApiVersionSet(versionSet)
.MapToApiVersion(new ApiVersion(class="code-number">1, class="code-number">0));
var v2 = app.MapGroup(class="code-string">"api/v{version:apiVersion}/products")
.WithApiVersionSet(versionSet)
.MapToApiVersion(new ApiVersion(class="code-number">2, class="code-number">0));
v1.MapGet(class="code-string">"/", () => Results.Ok(new[]
{
new { Id = class="code-number">1, Name = class="code-string">"Widget", Price = class="code-number">9.99m }
}));
v2.MapGet(class="code-string">"/", () => Results.Ok(new[]
{
new { Id = class="code-number">1, Name = class="code-string">"Widget", UnitPrice = class="code-number">9.99m, Currency = class="code-string">"USD" }
}));Bei wachsender Versionszahl empfiehlt es sich, die Routenregistrierungen in eigene Extension Methods auszulagern.
Versionsverhandlung und Standardversionen
`AssumeDefaultVersionWhenUnspecified = true` bedeutet, dass versionlose Anfragen auf Ihre Standardversion zurueckfallen. Praktisch, kann aber Probleme verdecken. Sichereres Produktionsmuster:
options.AssumeDefaultVersionWhenUnspecified = false;Das gibt `400 Bad Request` fuer nicht unterstuetzte Versionen zurueck und zwingt Konsumenten zur bewussten Versionswahl.
Breaking vs. Non-Breaking Changes
Non-Breaking (sicher ohne Versionierung hinzufuegbar)
Breaking (erfordert eine neue Version)
Bei APIs mit mehreren Clients richtet die Grauzone mehr Schaden an als offensichtliche Brueche. Ein Feld von `string` zu `string | null` ist technisch additiv, bringt aber Konsumenten ohne Null-Behandlung zum Absturz. Im Zweifelsfall: neue Version.
Deprecation-Strategie und Sunset-Header
Sie brauchen auch einen Plan fuer die Stilllegung alter Versionen. Markieren Sie Versionen als deprecated:
[ApiVersion(class="code-string">"class="code-number">1.0", Deprecated = true)]
[ApiVersion(class="code-string">"class="code-number">2.0")]
public class ProductsController : ControllerBase { }Oder in Minimal APIs:
var versionSet = app.NewApiVersionSet()
.HasDeprecatedApiVersion(new ApiVersion(class="code-number">1, class="code-number">0))
.HasApiVersion(new ApiVersion(class="code-number">2, class="code-number">0))
.Build();Veraltete Versionen funktionieren weiterhin, enthalten aber einen `api-deprecated-versions: 1.0`-Header. Ergaenzen Sie mit dem `Sunset`-Header (RFC 8594):
app.Use(async (context, next) =>
{
await next();
var apiVersion = context.GetRequestedApiVersion();
if (apiVersion?.MajorVersion == class="code-number">1)
{
context.Response.Headers[class="code-string">"Sunset"] = class="code-string">"Sat, class="code-number">01 Nov class="code-number">2025 class="code-number">00:class="code-number">00:class="code-number">00 GMT";
context.Response.Headers[class="code-string">"Link"] =
class="code-string">"</api/v2/products>; rel=\"successor-version\"";
}
});Das gibt Clients eine maschinenlesbare Frist und einen Verweis auf den Nachfolger.
OpenAPI-Dokumentation fuer versionierte APIs
Konfigurieren Sie Swagger/OpenAPI so, dass pro Version separate Dokumente generiert werden:
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc(class="code-string">"v1", new OpenApiInfo
{
Title = class="code-string">"Products API",
Version = class="code-string">"v1",
Description = class="code-string">"Urspruengliche Produkt-Endpunkte"
});
options.SwaggerDoc(class="code-string">"v2", new OpenApiInfo
{
Title = class="code-string">"Products API",
Version = class="code-string">"v2",
Description = class="code-string">"Erweiterte Produkt-Endpunkte mit Waehrungsunterstuetzung"
});
});
class=class="code-string">"code-comment">// In der Middleware-Pipeline
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint(class="code-string">"/swagger/v1/swagger.json", class="code-string">"Products API v1");
options.SwaggerEndpoint(class="code-string">"/swagger/v2/swagger.json", class="code-string">"Products API v2");
});Fuer automatische Versionserkennung nutzen Sie `IConfigureOptions
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider _provider;
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider)
=> _provider = provider;
public void Configure(SwaggerGenOptions options)
{
foreach (var description in _provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, new OpenApiInfo
{
Title = class="code-string">"Products API",
Version = description.ApiVersion.ToString(),
Description = description.IsDeprecated
? class="code-string">"Diese Version wurde als veraltet markiert."
: null
});
}
}
}Eine neue Version generiert nun automatisch das zugehoerige OpenAPI-Dokument.
Haeufige Fehler bei der API-Versionierung
Fazit
API-Versionierung dreht sich um Vertrauen. Die technische Umsetzung ist weniger wichtig als das organisatorische Commitment, Aenderungen klar zu kommunizieren und Versionen verantwortungsvoll stillzulegen. Waehlen Sie ein Schema, das zu Ihrem Oekosystem passt, automatisieren Sie Ihre Dokumentation, setzen Sie Sunset-Daten fest und halten Sie diese ein.
Wenn Sie eine Versionierungsstrategie fuer Ihre .NET-APIs entwerfen, unterstuetze ich Sie gerne beim richtigen Ansatz.
Verwandte Artikel
RESTful APIs mit ASP.NET Core entwickeln
Lernen Sie die Grundlagen für produktionsreife REST-APIs mit ASP.NET Core. Controller, Routing und Best Practices.
.NET Minimal APIs: Leichtgewichtige und schnelle Endpoints
Erstellen Sie schnelle Endpoints mit .NET Minimal APIs. Controller-freier Ansatz und Anwendungsfälle.
ASP.NET Core Middleware Pipeline: Ein tiefer Einblick
Tauchen Sie tief in die ASP.NET Core Middleware-Pipeline ein. Custom Middleware, Reihenfolge, Exception Handling und Rate Limiting.
Haben Sie ein Flutter-Projekt?
Ich entwickle hochleistungsfähige Flutter-Anwendungen für iOS, Android und Web.
Kontakt aufnehmen