Statuscodes Status301movedpermanentlyfield Microsoft Learn

Elena Vance
-
statuscodes status301movedpermanentlyfield microsoft learn

Why Swashbuckle is no longer the right choice Swashbuckle.AspNetCore was the de facto Swagger UI for .NET for years. Today it has three problems that are hard to work around: Microsoft removed Swashbuckle from the dotnet new webapi template in .NET 9 and replaced it with Microsoft.AspNetCore.OpenApi . That was the official signal. What replaced it Two components, one stack: Microsoft.AspNetCore.OpenApi — first-party document generator, ships with .NET 9+. Hooks directly into the ASP.NET Core endpoint data source. No XML comment parsing, no reflection gymnastics.

Generates OpenAPI 3.1 from the metadata you already declared.Scalar.AspNetCore — open-source API reference portal (MIT). Reads any OpenAPI 3.1 document and renders an interactive UI with OAuth2/PKCE auth, code generation in 15+ languages, and a clean dark/light mode design. They are independent. You can use the native generator with a different UI, or Scalar with a document from NSwag. In practice, using them together is the obvious choice. The migration — step by step 1. Remove Swashbuckle dotnet remove package Swashbuckle.AspNetCore Also remove any Swashbuckle.AspNetCore.* packages (annotations, filters, etc.).

Delete the AddSwaggerGen and UseSwagger / UseSwaggerUI calls from Program.cs . 2. Add the new packages dotnet add package Microsoft.AspNetCore.OpenApi dotnet add package Scalar.AspNetCore 3. Register document generation using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); // Native OpenAPI document generation builder.Services.AddOpenApi("v1", options => { options.AddDocumentTransformer((doc, _, _) => { doc.Info = new OpenApiInfo { Title = "Bookings API", Version = "1.0", Description = "Hotel booking management API.", }; return Task.CompletedTask; }); }); var app = builder.Build(); 4.

Map the endpoints if (app.Environment.IsDevelopment()) { // Serves /openapi/v1.json app.MapOpenApi("/openapi/v1.json"); // Serves Scalar UI at /scalar app.MapScalarApiReference(); } await app.RunAsync(); That is the minimum viable setup. Navigate to /scalar and you have an interactive API explorer backed by your live OpenAPI 3.1 document. Adding JWT Bearer authentication Swashbuckle had AddSecurityDefinition / AddSecurityRequirement . The native pipeline uses document transformers.

using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.OpenApi; using Microsoft.OpenApi.Models; internal sealed class BearerSecuritySchemeTransformer( IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer { public async Task TransformAsync( OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) { IEnumerable<AuthenticationScheme> schemes = await authenticationSchemeProvider.GetAllSchemesAsync(); if (!schemes.Any(s => s.Name == "Bearer")) { return; } document.Components ??= new OpenApiComponents(); document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>(); document.Components.SecuritySchemes["Bearer"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Description = "Enter your JWT token.", }; } } Register it before AddOpenApi : builder.Services.AddTransient<BearerSecuritySchemeTransformer>(); builder.Services.AddOpenApi("v1", options => { options.AddDocumentTransformer<BearerSecuritySchemeTransformer>(); // ...

}); Note: the transformer must be registered in DI before AddOpenApi is called, or the DI resolution will fail at startup. OAuth2 Authorization Code + PKCE If your API is protected by an OAuth2 provider (Keycloak, Entra ID, Auth0), you can configure Scalar to authenticate directly in the browser — no Postman needed. app.MapScalarApiReference(opts => { opts.WithTitle("Bookings API"); opts.AddAuthorizationCodeFlow("OAuth2", flow => { flow.WithClientId("bookings-frontend-client") .WithSelectedScopes("openid", "profile") .WithPkce(Pkce.Sha256); }); }); Scalar shows a Sign in button. The flow runs entirely in the browser.

The resulting token is automatically attached to every test request. Use a public (frontend) client. Never configure your backend confidential client here — it would expose the client secret in a browser context. Multi-version APIs One document per major version. Each call to AddOpenApi registers an independent document: builder.Services.AddOpenApi("v1", options => { /* ... */ }); builder.Services.AddOpenApi("v2", options => { /* ... */ }); app.MapOpenApi("/openapi/v1.json"); app.MapOpenApi("/openapi/v2.json"); app.MapScalarApiReference(opts => { opts.WithEndpointPrefix("/scalar/{documentName}"); }); The default Scalar route (/scalar/{documentName} ) handles version switching in the UI.

Common gotchas .Produces<T>() is required — there is no fallback inference Swashbuckle could sometimes infer response types by inspecting the action return type. The native pipeline does not. If you omit .Produces<T>() , the operation has no response schema.

// Missing .Produces<BookingResponse>() → no response type in the OpenAPI document app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("Returns a booking by ID.") .ProducesProblem(StatusCodes.Status404NotFound); // .Produces<BookingResponse>() ← you must add this // Correct app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("Returns a booking by ID.") .Produces<BookingResponse>() .ProducesProblem(StatusCodes.Status404NotFound); Use TypedResults , not Results Results.Ok(value) returns IResult at compile time — the native pipeline cannot determine the response type. TypedResults.Ok(value) returns Ok<T> — the type is known statically and shows up in the OpenAPI schema automatically.

// BAD — type lost at compile time return Results.Ok(booking); // GOOD — type preserved, OpenAPI schema complete return TypedResults.Ok(booking); Protect the docs endpoint in non-development environments app.MapOpenApi() and app.MapScalarApiReference() create unauthenticated endpoints by default. In staging or production, protect them explicitly: app.MapOpenApi("/openapi/v1.json") .RequireAuthorization("InternalDeveloper"); app.MapScalarApiReference() .RequireAuthorization("InternalDeveloper"); Or gate the entire block with IsDevelopment() and add explicit staging config. Transformers run in registration order Document transformers execute in the order they are registered. If transformer B depends on data set by transformer A, register A first.

This tripped me up with a security scheme transformer that assumed doc.Components was already initialized. Scalar vs Swagger UI — feature comparison If you use a framework that already handles this Rolling the transformer pipeline manually gets repetitive across projects. If you work with the open-source Granit framework for .NET, all of this is pre-wired in the Granit.Http.ApiDocumentation module: security schemes, OAuth2 PKCE, multi-version documents, and production protection are all configured from a single appsettings.json section.

{ "ApiDocumentation": { "Title": "Bookings API", "MajorVersions": [1, 2], "AuthorizationPolicy": "InternalDeveloper", "OAuth2": { "AuthorizationUrl": "https://auth.example.com/.../auth", "TokenUrl": "https://auth.example.com/.../token", "ClientId": "bookings-frontend-client" } } } Worth a look if you are building a new modular .NET backend from scratch. Summary The migration from Swashbuckle to Scalar on .NET 10 is straightforward once you know the transformer pattern.

The short version: - Remove Swashbuckle.AspNetCore , addMicrosoft.AspNetCore.OpenApi +Scalar.AspNetCore - Replace AddSwaggerGen withAddOpenApi + document transformer for metadata - Replace UseSwagger /UseSwaggerUI withMapOpenApi +MapScalarApiReference - Switch all handlers from Results.* toTypedResults.* - Add .Produces<T>() explicitly on every endpoint - Protect the docs endpoints outside of development The result: OpenAPI 3.1, a modern interactive UI, native OAuth2 PKCE, and no more dependency on an archived package. Have questions or migration tips to share? Drop them in the comments. Top comments (0)

People Also Asked

List of HTTP status codes - Wikipedia?

// Missing .Produces<BookingResponse>() → no response type in the OpenAPI document app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("Returns a booking by ID.") .ProducesProblem(StatusCodes.Status404NotFound); // .Produces<BookingResponse>() ← you must add this // Correct app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("...

HTTP Status Code Overview - Internet Information... |MicrosoftLearn?

// Missing .Produces<BookingResponse>() → no response type in the OpenAPI document app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("Returns a booking by ID.") .ProducesProblem(StatusCodes.Status404NotFound); // .Produces<BookingResponse>() ← you must add this // Correct app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("...

Redirection Status Codes: 301, 302... | Baeldung on Computer Science?

// Missing .Produces<BookingResponse>() → no response type in the OpenAPI document app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("Returns a booking by ID.") .ProducesProblem(StatusCodes.Status404NotFound); // .Produces<BookingResponse>() ← you must add this // Correct app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("...

418 I'm a teapot - HTTP status code explained?

// Missing .Produces<BookingResponse>() → no response type in the OpenAPI document app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("Returns a booking by ID.") .ProducesProblem(StatusCodes.Status404NotFound); // .Produces<BookingResponse>() ← you must add this // Correct app.MapGet("/bookings/{id:guid}", GetBookingAsync) .WithName("GetBooking") .WithSummary("...

Swashbuckle Is Dead. Here's How to Migrate to... - DEV Community?

Common gotchas .Produces<T>() is required — there is no fallback inference Swashbuckle could sometimes infer response types by inspecting the action return type. The native pipeline does not. If you omit .Produces<T>() , the operation has no response schema.