API-Versionierungsstrategien in .NET

10 Min. Lesezeit9. März 2026
API versioning .NETAsp.Versioning.NET API version strategyAPI deprecationREST API versioningMinimal API versioningAPI backward compatibilitySwagger versioning

# 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/products

Der 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.0

Haelt 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.0

Haelt 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+json

Theoretisch 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:

csharp
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.Http

Grundkonfiguration

csharp
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

csharp
[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:

csharp
[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

csharp
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:

csharp
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)

  • Neue optionale Felder in einer Antwort hinzufuegen
  • Einen neuen Endpunkt hinzufuegen
  • Optionale Query-Parameter hinzufuegen
  • Eine Validierungseinschraenkung lockern
  • Breaking (erfordert eine neue Version)

  • Ein Feld entfernen oder umbenennen
  • Den Datentyp eines Feldes aendern
  • Die Antwortstruktur aendern (z.B. in einen neuen Umschlag einbetten)
  • Das Fehlerantwortformat aendern
  • Ein zuvor optionales Feld zur Pflicht machen
  • Die Semantik von HTTP-Methoden oder Statuscodes aendern
  • 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:

    csharp
    [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:

    csharp
    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):

    csharp
    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:

    csharp
    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`:

    csharp
    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

  • **Zu aggressive Versionierung** -- fuer jede kleine Aenderung eine neue Version zu erstellen fragmentiert die API-Oberflaeche und vervielfacht den Wartungsaufwand. Neue Versionen nur fuer echte Breaking Changes.
  • **Kein Deprecation-Zeitplan** -- wenn veraltete Versionen ewig bestehen bleiben, muessen Sie jede jemals ausgelieferte Version dauerhaft warten. Explizite Sunset-Daten festlegen.
  • **Inkonsistente Versionierung ueber Endpunkte hinweg** -- einzelne Endpunkte statt der gesamten API zu versionieren erzeugt eine kaum dokumentierbare Matrix.
  • **Client-seitige Auswirkungen vergessen** -- SDKs, Client-Bibliotheken und Integrationstests muessen ebenfalls wissen, welche Version sie ansprechen.
  • **Versionsnummern an den Deployment-Rhythmus koppeln** -- API-Versionen repraesentieren Vertragsaenderungen, nicht Release-Zyklen.
  • **Versions-Discovery vernachlaessigen** -- wenn Clients verfuegbare Versionen nicht programmatisch erkennen koennen, rufen sie unweigerlich die falsche auf.
  • 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

    Haben Sie ein Flutter-Projekt?

    Ich entwickle hochleistungsfähige Flutter-Anwendungen für iOS, Android und Web.

    Kontakt aufnehmen