Start Debugging

Scalar vs Swagger UI für OpenAPI-Dokumentation in ASP.NET Core 11

Scalar liefert 1,02 MiB gzip-komprimiertes JavaScript und einen deutlich besseren Request-Builder. Swagger UI liefert 514 KiB und rendert OpenAPI 3.2, was .NET 11 inzwischen standardmäßig ausgibt. Gemessene Payloads, die 3.2-Lücke, Endpoint-Routing auf beiden Seiten und die Authentifizierungsdetails, die entscheiden.

Wählen Sie Scalar (Scalar.AspNetCore 2.16.20) für eine neue .NET 11 API, wenn die Leser Ihrer Dokumentation außerhalb Ihres Unternehmens sitzen, denn der Request-Builder, die Codebeispiele in mehreren Sprachen und die Suche sind wirklich besser als alles, was Swagger UI bietet. Wählen Sie Swagger UI (Swashbuckle.AspNetCore.SwaggerUI 10.2.3, mit swagger-ui 5.32.7 im Paket), wenn Sie die kleinere Payload wollen, wenn Sie sich auf den bereits konfigurierten OAuth2-Redirect-Flow verlassen, oder wenn Sie heute verlässliches Rendering von OpenAPI 3.2 brauchen, denn .NET 11 gibt standardmäßig 3.2 aus und die 3.2-Arbeit bei Scalar ist weiterhin ein offenes Issue. Beide stehen unter MIT-Lizenz, beide sind reine Renderer ohne Einfluss auf Ihr OpenAPI-Dokument, und Microsofts Empfehlung lautet, dass keiner von beiden in der Produktion erreichbar sein sollte.

Alles hier Gemessene lief gegen das .NET SDK 10.0.201 mit den genannten Paketversionen, am 2026-08-15. Die API-Oberfläche ist von .NET 8 bis .NET 11 identisch, weil beide Pakete net8.0-, net9.0- und net10.0-Assemblies ausliefern und eine Framework-Referenz auf Microsoft.AspNetCore.App setzen, statt eine Laufzeit festzunageln.

Der Vergleich, den man zu ziehen glaubt, ist nicht der entscheidende

Seit .NET 9 enthält dotnet new webapi kein Swashbuckle mehr. Microsoft.AspNetCore.OpenApi erzeugt das Dokument und ist mit Trimming und Native AOT kompatibel. Die Entscheidung lautet also nicht “Swashbuckle oder Scalar”, sondern “welches JavaScript-Bundle rendert das Dokument, das mein Framework ohnehin produziert”. Wer für die Generierung noch auf Swashbuckles SwaggerGen setzt, trifft eine separate Entscheidung, beschrieben in OpenAPI ohne Swashbuckle in ASP.NET Core 11 bereitstellen.

Diese Unterscheidung hat eine praktische Folge. Das Metapaket Swashbuckle.AspNetCore zieht Swashbuckle.AspNetCore.Swagger, SwaggerGen und Microsoft.Extensions.ApiDescription.Server mit der Oberfläche herein. Wer nur die Oberfläche will, referenziert direkt Swashbuckle.AspNetCore.SwaggerUI, und es kommt nichts weiter mit.

<!-- .NET 11, C# 14: the UI only, no second document generator -->
<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="11.0.0" />
  <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.2.3" />
</ItemGroup>
<!-- .NET 11, C# 14: the Scalar equivalent, one package, zero NuGet dependencies -->
<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="11.0.0" />
  <PackageReference Include="Scalar.AspNetCore" Version="2.16.20" />
</ItemGroup>

Die Matrix

Scalar 2.16.20Swagger UI 5.32.7 (Swashbuckle 10.2.3)
Übertragene Bytes beim ersten Laden (gzip)1.071.277526.322
Nach dem Entpacken geparstes JavaScript3.711 KB1.794 KB
Registrierungapp.MapScalarApiReference()app.UseSwaggerUI(...) oder app.MapSwaggerUI(...)
Endpoint-RoutingJa, seit 1.xJa, seit 10.2.0 (Mai 2026)
OpenAPI 3.2Der Parser kommt damit zurecht, vollständige Unterstützung in einem offenen IssueGrundlegende Unterstützung seit swagger-ui 5.32.0
CodebeispieleÜber 20 Ziele (curl, fetch, axios, Python, Go, Java, PHP, Ruby und mehr)curl für die gerade abgesendete Anfrage
Asset-CachingCache-Control: no-cache plus ETag, fest im CodeETag als Standard, max-age wenn Sie CacheLifetime setzen
Gespeicherte ZugangsdatenpersistAuth schreibt in den Local StoragePersistAuthorization im Konfigurationsobjekt
Try It über Origin-Grenzenoptionale proxyUrldirekter Browser-fetch, CORS ist Ihr Problem
Themes12 eingebaute Themes, customCss, PluginsInjectStylesheet, InjectJavascript, das swagger-ui-Plugin-System
LizenzMITMIT

Was jede Oberfläche den Browser kostet, gemessen

Beide Pakete betten ihre Assets als gzip-Streams in die Assembly ein und geben diese Bytes direkt an einen Client weiter, der Accept-Encoding: gzip ankündigt. Scalars ASP.NET Core Integration prüft IsGzipAccepted() und setzt Content-Encoding sowie Vary: Accept-Encoding aus dem gespeicherten Asset. Die Middleware der Swashbuckle-Oberfläche trägt dieselbe Mechanik (IsGZipAccepted, ein GZipStream im Dekomprimierungsmodus für den seltenen Client, der ablehnt). Die gespeicherten Ressourcengrößen sind also die Übertragungsgrößen, und Sie können sie aus den Paketen auslesen, ohne etwas auszuführen:

// .NET SDK 10.0.201, run as a file-based app: dotnet run res.cs <dll>
using System.Reflection;

var asm = Assembly.LoadFrom(args[0]);
foreach (var name in asm.GetManifestResourceNames())
{
    using var s = asm.GetManifestResourceStream(name);
    Console.WriteLine($"{s?.Length,10}  {name}");
}

Scalar liefert drei Assets aus, von denen nur zwei Code sind:

   1070166  ScalarStaticAssets.scalar.js
      1111  ScalarStaticAssets.scalar.aspnetcore.js
       533  ScalarStaticAssets.favicon.svg

Swashbuckles index.html lädt das Bundle, das Standalone-Preset, das Stylesheet und den eigenen Initializer:

    421507  swagger-ui-bundle.js
     77731  swagger-ui-standalone-preset.js
     26499  swagger-ui.css
       433  index.js
       152  index.css
       739  index.html

Das sind 1.071.277 Bytes für Scalar gegenüber 526.322 Bytes für Swagger UI, ein Faktor von 2,0 auf der Leitung. Entpackt sind scalar.js 3.708.228 Bytes JavaScript, die der Browser parsen muss, gegenüber 1.793.552 Bytes für Swagger UIs Bundle plus Preset. Die modern wirkende Option ist die schwere, was das Gegenteil dessen ist, was die meisten Artikel nahelegen.

Zwei Einschränkungen, bevor Sie das zu hoch gewichten. Erstens ist das ein Entwicklungswerkzeug: Die Bytes landen über Loopback auf Ihrem Rechner, einmal pro Kaltstart. Zweitens liegt Swashbuckles swagger-ui.js (92.466 Bytes) ungenutzt im Paket, die Standardseite lädt es nicht, die Zahl oben ist also das tatsächlich Geladene, nicht das Ausgelieferte. Wenn Sie eine der Oberflächen über ein echtes Netz ausliefern, hilft der Vergleich der Antwortkomprimierung hier nicht: Beide Pakete haben diese Assets bereits selbst komprimiert, und eine Antwort mit Content-Encoding: gzip noch einmal zu komprimieren ist nichts, was die Middleware tun wird.

Das Caching ist der Teil, der täglich stört. SwaggerUIOptions.CacheLifetime dokumentiert seinen Standardwert als “0 days (ETags are used to check if resources have been updated)”, ab Werk revalidieren also beide Oberflächen. Der Unterschied: Swashbuckle lässt echtes Caching zu, Scalar nicht. Dessen Handler für statische Assets setzt fest Cache-Control: no-cache und beantwortet ein passendes If-None-Match mit einem 304. Sie zahlen einen Roundtrip pro Asset pro Seitenaufruf, dauerhaft.

// Program.cs -- .NET 11, C# 14, Swashbuckle.AspNetCore.SwaggerUI 10.2.3
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/openapi/v1.json", "v1");
    options.CacheLifetime = TimeSpan.FromDays(7); // 304s become cache hits
});

Der .NET 11 Haken: Ihr Dokument ist jetzt 3.2

Das ist die Tatsache, die im August 2026 die Entscheidung tragen sollte, und fast niemand hat sie aufgeschrieben. Microsoft Learn ist eindeutig: “Starting in .NET 11, the default OpenAPI version for generated documents is 3.2. In .NET 10, the default is 3.1.” Aktualisieren Sie eine API von .NET 10 auf .NET 11, ohne sonst etwas zu ändern, und das Dokument, das Ihre Oberfläche rendern muss, wechselt die Spezifikationsversion.

Auf der Swagger-UI-Seite brachte swagger-ui 5.32.0 (27. Februar 2026) “basic OpenAPI 3.2.0 support”, und Swashbuckle 10.2.3 liefert 5.32.7 mit, der Renderer weiß also zumindest, was er vor sich hat. Auf der Scalar-Seite versteht @scalar/openapi-parser 3.2, aber das Tracking-Issue scalar/scalar#6715 ist weiterhin offen, mit “set OpenAPI 3.2 as the default version” und dem Rendering tief verschachtelter Tags in der Seitenleiste als offene Punkte, Stand der letzten Aktualisierung vom 30. Juni 2026.

In der Praxis ändert sich ein aus Minimal-API-Endpunkten erzeugtes Dokument zwischen 3.1 und 3.2 nur minimal, die meisten Anwendungen werden gar keinen Unterschied sehen. Falls doch eine Seitenleiste falsch gruppiert oder ein Schema leer rendert, nageln Sie die Version fest, statt einen Bug gegen die Oberfläche zu melden:

// Program.cs -- .NET 11, C# 14
builder.Services.AddOpenApi(options =>
{
    // .NET 11 defaults to OpenApi3_2; pin 3.1 while a renderer catches up
    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});

Denselben Schalter gibt es für die Generierung zur Buildzeit über die MSBuild-Eigenschaft OpenApiGenerateDocumentsOptions mit --openapi-version OpenApi3_1. Das Festnageln kostet heute nichts: Nichts in einem von ASP.NET Core erzeugten Dokument hängt bisher an 3.2-Funktionen.

Middleware oder Endpunkt, inzwischen auf beiden Seiten

Das stärkste architektonische Argument für Scalar war lange, dass MapScalarApiReference einen Endpunkt registriert, während UseSwaggerUI Middleware registriert, und Middleware die Anfrage beendet, bevor das Endpoint-Routing mitreden kann. Dieses Argument ist im Mai 2026 verfallen. Swashbuckle 10.2.0 ergänzte MapSwaggerUI und MapReDoc “to support endpoint routing”. Beide Oberflächen können jetzt Endpunkt-Metadaten tragen, in der EndpointDataSource erscheinen und Routing-Konventionen direkt entgegennehmen:

// Program.cs -- .NET 11, C# 14
// Scalar: MapScalarApiReference returns an IEndpointConventionBuilder
app.MapScalarApiReference()
   .RequireAuthorization("ApiDocsPolicy");

// Swashbuckle 10.2.0+: same shape
app.MapSwaggerUI()
   .RequireAuthorization("ApiDocsPolicy");

Hinter einem Reverse Proxy ist zu beachten: Scalars HTML-Endpunkt leitet eine Anfrage auf /scalar mit einem 301 auf /scalar/ um, damit die relativen Asset-Pfade auflösen, und Swashbuckles Middleware leitet eine Anfrage auf den blanken Routenpräfix mit einem 301 auf index.html um. Ein Integrationstest, der auf dem blanken Pfad einen 200 erwartet, scheitert bei beiden.

Authorize, und was nach dem Klick passiert

Beide Oberflächen lesen die Sicherheitsschemas aus dem Dokument, keine erfindet sie. Scalars eigene Dokumentation ist deutlich: Ihr OpenAPI-Dokument muss die Schemas bereits enthalten, damit Scalar mit ihnen arbeiten kann. Wenn sie dort nicht stehen, ist die Anleitung zu Operation- und Schema-Transformern der passende Mechanismus.

Unterschiedlich ist die Ergonomie danach. Scalar füllt Zugangsdaten aus der serverseitigen Konfiguration vor und kann sie über Neuladungen hinweg behalten:

// Program.cs -- .NET 11, C# 14, Scalar.AspNetCore 2.16.20
app.MapScalarApiReference(options =>
{
    options.AddPreferredSecuritySchemes("Bearer")
           .AddHttpAuthentication("Bearer", auth => auth.WithToken(devToken));
    options.PersistentAuthentication = true;
});

Das Gegenstück in Swagger UI liegt im Konfigurationsobjekt und, für OAuth2, in der Seite oauth2-redirect.html, die Swashbuckle für Sie einbettet (664 Bytes Redirect-Skript, das seit einem Jahrzehnt im Einsatz ist):

// Program.cs -- .NET 11, C# 14, Swashbuckle.AspNetCore.SwaggerUI 10.2.3
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/openapi/v1.json", "v1");
    options.OAuthClientId("dev-client");
    options.OAuthUsePkce();
    options.EnablePersistAuthorization();
});

Die eine Fähigkeit, die Scalar hat und Swagger UI nicht, ist proxyUrl. Swagger UIs Try It feuert ein fetch vom Origin der Dokumentation ab, eine API auf einem anderen Origin ohne großzügiges CORS erzeugt also einen Browserfehler, der wie ein Serverausfall aussieht. Scalar kann die Anfrage stattdessen über einen Proxy leiten. Wenn Ihre Dokumentation getrennt von der API gehostet wird, entscheidet diese eine Option.

Die Codebeispiele sind der eigentliche Produktunterschied

Swagger UI zeigt Ihnen den curl-Befehl der gerade ausgeführten Anfrage. Scalar rendert die Anfrage in jedem ihm bekannten Client, bevor Sie irgendetwas senden: Shell (curl, httpie), JavaScript (fetch, axios, jquery), Node, Python, Go, Java, Ruby, PHP und mehr, gesteuert über hiddenClients und defaultHttpClient. Für eine interne API, deren Leser dieselben Menschen sind, die sie geschrieben haben, ist das Dekoration. Für eine öffentliche API, deren Leser gerade entscheidet, ob Ihr Produkt leicht zu integrieren ist, ist es die ganze Seite.

Scalar liefert außerdem searchHotKey (standardmäßig CMD/CTRL+K), zwölf eingebaute Themes, customCss und einen Hook unter /scalar/config.js für beliebige Client-Konfiguration. Die Anpassung von Swagger UI läuft über InjectStylesheet, InjectJavascript und das swagger-ui-Plugin-System, das mächtiger und deutlich unangenehmer ist, und das ist die ehrliche Zusammenfassung des gesamten Vergleichs.

Wann welche Wahl richtig ist

Wählen Sie Scalar, wenn die Dokumentation Teil des Produkts ist, wenn die Leser außerhalb Ihres Teams sitzen, wenn Sie den Request-Builder und die Codebeispiele wollen, oder wenn die Dokumentation auf einem anderen Origin als die API liegt und Sie den Proxy brauchen.

Wählen Sie Swagger UI, wenn Sie die kleinste Payload und echtes max-age-Caching wollen, wenn Sie ein bestehendes OAuth2-Setup haben, das funktioniert, wenn jemand im Team von einem swagger-ui-Plugin abhängt, oder wenn Sie den Renderer mit expliziter 3.2-Unterstützung wollen, während .NET 11 standardmäßig 3.2 ausgibt.

Wählen Sie keinen von beiden und nehmen Sie Swashbuckle.AspNetCore.ReDoc oder eine Editor-Erweiterung, wenn das Dokument von generierten Clients statt von Menschen konsumiert wird. Es gibt keine Regel, die einer API eine gerenderte Referenz vorschreibt.

Was auch immer Sie wählen, Microsoft Learn formuliert die Sicherheitsposition klar: OpenAPI-Benutzeroberflächen sollten nur in Entwicklungsumgebungen aktiviert sein. Beide Pakete machen daraus einen einzeiligen Umgebungs-Guard, und die schrittweise Fassung dieser Einrichtung, inklusive Absicherung gegen Produktion und Offline-Assets, steht in der Scalar-Anleitung.

Die Details, die für Sie entscheiden

Zwischen zwei MIT-lizenzierten Renderern desselben Dokuments ist das eine umkehrbare Entscheidung: Eine Zeile in Program.cs und eine Paketreferenz bringen Sie in etwa fünf Minuten in jede Richtung. Entscheiden Sie nach dem Leser, nicht nach dem Framework.

Verwandte Artikel

Quellen

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Zurück