OpenAPI Documentation in ASP.NET Core 9/10: A Complete Guide
Learn how to generate and customize OpenAPI (Swagger) documentation in ASP.NET Core 9/10.
20+ years shipping production .NET services in enterprise systems. Written from production experience, not tutorials.
- ✓Basic knowledge of ASP.NET Core and C#
- ✓Familiarity with RESTful API concepts
- ✓Visual Studio or VS Code with .NET 9 or 10 SDK installed
- OpenAPI (formerly Swagger) provides a standardized way to describe REST APIs.
- ASP.NET Core 9/10 uses the
Microsoft.AspNetCore.OpenApipackage for OpenAPI generation. - You can customize document info, security schemes, and operation descriptions.
- Tools like Swagger UI and Scalar allow interactive API testing.
- Proper OpenAPI documentation improves developer experience and API adoption.
Think of OpenAPI as a menu for your API. Just as a restaurant menu tells you what dishes are available and how to order them, OpenAPI tells developers what endpoints exist, what parameters they need, and what responses to expect. Swagger UI is like a waiter who can take your order and bring you the food—it lets you test the API directly from the browser.
In modern web development, APIs are the backbone of communication between services. However, an API is only as good as its documentation. Without clear, up-to-date documentation, developers struggle to integrate with your API, leading to frustration and bugs. OpenAPI (formerly known as Swagger) solves this by providing a machine-readable specification for RESTful APIs. ASP.NET Core 9 and 10 have embraced OpenAPI with first-class support through the Microsoft.AspNetCore.OpenApi package, making it easier than ever to generate and customize documentation.
Imagine you're building a public API for a weather service. You want external developers to easily understand how to get forecasts, submit weather data, and handle errors. With OpenAPI, you can define all endpoints, request/response schemas, authentication methods, and even provide sample requests. Tools like Swagger UI and Scalar then render this specification into an interactive documentation page where developers can test endpoints directly.
In this tutorial, you'll learn how to set up OpenAPI in ASP.NET Core 9/10, customize the generated document, add security definitions, version your API, and avoid common pitfalls. We'll also cover a real-world production incident caused by misconfigured OpenAPI and provide a debugging guide for when things go wrong. By the end, you'll be able to produce professional-grade API documentation that delights developers.
Setting Up OpenAPI in ASP.NET Core 9/10
To get started with OpenAPI in ASP.NET Core 9/10, you need to add the Microsoft.AspNetCore.OpenApi package. This package is included in the ASP.NET Core shared framework starting from .NET 9, but you may need to install it explicitly for earlier versions. For .NET 9 and 10, it's part of the Microsoft.AspNetCore.App framework, so no extra NuGet package is required.
First, create a new ASP.NET Core Web API project:
``bash dotnet new webapi -n OpenApiDemo cd OpenApiDemo ``
In Program.cs, add the OpenAPI services and middleware:
```csharp var builder = WebApplication.CreateBuilder(args);
// Add services to the container builder.Services.AddControllers(); builder.Services.AddOpenApi(); // Registers OpenAPI document generator
var app = builder.Build();
// Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.MapOpenApi(); // Serves the OpenAPI document at /openapi/{documentName}.json }
app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); ```
By default, the OpenAPI document is served at /openapi/v1.json. You can also configure Swagger UI for interactive testing. Add the Swagger UI middleware:
``csharp if (app.``Environment.IsDevelopment()) { app.MapOpenApi(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/openapi/v1.json", "My API V1"); }); }
Now run the application and navigate to /swagger to see the interactive UI. You should see your API endpoints listed.
AddOpenApi() and MapOpenApi() in the pipeline. Swagger UI is optional but recommended for interactive testing.Customizing the OpenAPI Document
The default OpenAPI document is minimal. You can customize it by providing options to AddOpenApi(). For example, you can set the title, version, description, and contact information. This is done using a document transformer or by configuring the OpenApiOptions.
Here's how to customize the document info:
``csharp builder.Services.AddOpenApi(options => { options.AddDocumentTransformer((document, context, cancellationToken) => { document.Info = ``new() { Title = "Weather Forecast API", Version = "v1", Description = "An API to retrieve weather forecasts and historical data.", Contact = new() { Name = "API Support", Email = "support@example.com" } }; return Task.CompletedTask; }); });
You can also add external documentation links, terms of service, and license information. Additionally, you can customize the document name and version via the MapOpenApi method:
``csharp app.``MapOpenApi(); // Default document name is "v1" // Or specify a custom name: app.MapOpenApi("myapi"); // Served at /openapi/myapi.json
For more advanced customization, you can use operation transformers to modify individual endpoints. For example, to add a common response description:
``csharp options.AddOperationTransformer((operation, context, cancellationToken) => { operation.Description = "This endpoint is used for weather data."; return Task.CompletedTask; }); ``
Remember that transformers are called for each document or operation, so use them judiciously to avoid performance issues.
Adding Security Definitions (JWT, API Key, etc.)
Most real-world APIs require authentication. OpenAPI allows you to define security schemes that Swagger UI can use to send credentials. For JWT bearer authentication, you need to add a security scheme and a global security requirement.
First, install the Microsoft.AspNetCore.Authentication.JwtBearer package if not already present. Then configure authentication in Program.cs:
``csharp builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://your-identity-server"; options.Audience = "your-api"; }); ``
Now, add the OpenAPI security scheme:
``csharp builder.Services.AddOpenApi(options => { options.AddDocumentTransformer((document, context, cancellationToken) => { document.Components ??= ``new(); document.Components.SecuritySchemes = new Dictionary<string, OpenApiSecurityScheme> { ["Bearer"] = new() { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "JWT Authorization header using the Bearer scheme." } }; document.Security = new List<OpenApiSecurityRequirement> { new() { [new OpenApiSecurityScheme { Reference = new() { Id = "Bearer" } }] = new List<string>() } }; return Task.CompletedTask; }); });
This tells Swagger UI to show an "Authorize" button where users can paste their JWT token. The token will then be sent in the Authorization header for all requests.
For API key authentication, you can define a security scheme of type ApiKey with the header name. Similarly, for OAuth2, you can define the flows.
API Versioning with OpenAPI
As your API evolves, you'll need to support multiple versions. ASP.NET Core provides the Asp.Versioning.Mvc package for API versioning, and you can integrate it with OpenAPI to generate separate documents for each version.
First, install the package:
``bash dotnet add package Asp.Versioning.Mvc ``
Configure versioning in Program.cs:
``csharp builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ReportApiVersions = true; options.ApiVersionReader = new ``UrlSegmentApiVersionReader(); });
Then, add OpenAPI for each version. You can use a document transformer to set the version dynamically:
``csharp builder.Services.AddOpenApi("v1", options => { / configure v1 / }); builder.Services.AddOpenApi("v2", options => { / configure v2 / }); ``
In the pipeline, map each document:
``csharp app.MapOpenApi("v1"); app.MapOpenApi("v2"); ``
For Swagger UI, you can add multiple endpoints:
``csharp app.UseSwaggerUI(c => { c.SwaggerEndpoint("/openapi/v1.json", "API V1"); c.SwaggerEndpoint("/openapi/v2.json", "API V2"); }); ``
Now, your controllers can specify the API version via attribute:
``csharp [ApiVersion("1.0")] [Route("api/v{version:apiVersion}/[controller]")] [ApiController] public class WeatherController : ControllerBase { [HttpGet] public IActionResult ``Get() => Ok("Version 1"); }
This approach ensures that each version has its own OpenAPI document, making it clear to consumers what endpoints are available in each version.
options.ReportApiVersions = true and adding a Deprecated attribute to controllers. OpenAPI will mark them accordingly.AddOpenApi("v1") and MapOpenApi("v1") for each version.Using Data Annotations and XML Comments
To enrich your OpenAPI documentation with descriptions, examples, and constraints, you can use data annotations on your models and controllers, as well as XML comments. This makes the generated document much more informative.
First, enable XML documentation generation in your .csproj file:
``xml <PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile> <NoWarn>$(NoWarn);1591</NoWarn> </PropertyGroup> ``
Then, in Program.cs, configure OpenAPI to include XML comments:
``csharp builder.Services.AddOpenApi(options => { options.AddDocumentTransformer((document, context, cancellationToken) => { // Include XML comments var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly); foreach (var xmlFile in xmlFiles) { options.IncludeXmlComments(xmlFile); } return Task.CompletedTask; }); }); ``
Now, add XML comments to your controllers and models:
``csharp /// <summary> /// Retrieves the current weather forecast for a given city. /// </summary> /// <param name="city">The name of the city (e.g., "London").</param> /// <returns>A weather forecast object.</returns> [HttpGet("{city}")] public IActionResult GetWeather(string city) { // ... } ``
For models, use data annotations like [Required], [StringLength], and [Range] to specify constraints:
```csharp public class WeatherForecast { /// <summary> /// The date of the forecast. /// </summary> [Required] public DateTime Date { get; set; }
/// <summary> /// Temperature in Celsius. /// </summary> [Range(-50, 50)] public int TemperatureC { get; set; } } ```
These annotations and comments will appear in the OpenAPI document, providing clear guidance to API consumers.
Testing OpenAPI Documentation with Integration Tests
To ensure your OpenAPI documentation stays accurate as your API evolves, write integration tests that validate the generated document. This catches issues like missing endpoints, incorrect schemas, or broken security definitions.
First, add the Microsoft.AspNetCore.Mvc.Testing package:
``bash dotnet add package Microsoft.AspNetCore.Mvc.Testing ``
Create a test class that uses WebApplicationFactory:
```csharp public class OpenApiTests : IClassFixture<WebApplicationFactory<Program>> { private readonly HttpClient _client;
public OpenApiTests(WebApplicationFactory<Program> factory) { _client = factory.CreateClient(); }
[Fact] public async Task GetOpenApiDocument_ReturnsSuccess() { // Arrange var request = "/openapi/v1.json";
// Act var response = await _client.GetAsync(request);
// Assert response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); Assert.Contains("openapi", content); }
[Fact] public async Task OpenApiDocument_ContainsWeatherEndpoint() { var response = await _client.GetAsync("/openapi/v1.json"); var content = await response.Content.ReadAsStringAsync(); Assert.Contains("weather", content, StringComparison.OrdinalIgnoreCase); } } ```
You can also validate the JSON structure using a library like Newtonsoft.Json.Schema or JsonSchema.Net. For more advanced testing, parse the OpenAPI document and verify specific paths, schemas, and security requirements.
Integration tests should be part of your CI/CD pipeline to catch regressions early.
Alternative Tools: Scalar and NSwag
While Swagger UI is the most common tool for visualizing OpenAPI documents, alternatives like Scalar and NSwag offer different features. Scalar provides a modern, clean UI with built-in authentication support and better performance. NSwag can generate client code for multiple languages.
To use Scalar, install the Scalar.AspNetCore package:
``bash dotnet add package Scalar.AspNetCore ``
Then, replace Swagger UI with Scalar in Program.cs:
``csharp if (app.``Environment.IsDevelopment()) { app.MapOpenApi(); app.MapScalarApiReference(); // Serves Scalar UI at /scalar/v1 }
Scalar automatically reads the OpenAPI document and provides an interactive interface. It also supports theming and customization.
NSwag, on the other hand, is not just a UI tool but a code generator. You can use it to generate C# client code from your OpenAPI document. Install the NSwag.AspNetCore package:
``bash dotnet add package NSwag.AspNetCore ``
Then, configure NSwag:
```csharp builder.Services.AddOpenApiDocument(); // NSwag's document generator
if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi3(); } ```
NSwag also provides a middleware to serve the document and UI. However, note that NSwag uses its own document generator, not the built-in Microsoft.AspNetCore.OpenApi. Choose the tool that best fits your needs.
The Missing Authorization Header: A Swagger UI Outage
- Always verify that the OpenAPI security scheme matches the actual authentication middleware configuration.
- Use integration tests to validate that Swagger UI requests succeed with a valid token.
- Document the exact header format (e.g., 'Authorization: Bearer <token>') in your API documentation.
- Consider using tools like Scalar that allow you to test with custom headers easily.
- Monitor Swagger UI usage and set up alerts for authentication failures.
/swagger/v1/swagger.json. Verify that your API controllers and actions are public and have appropriate HTTP attributes.app.UseSwaggerUI() is called). Verify that the Swagger endpoint URL is correct.AddOpenApi().CacheOutput(). Consider filtering out internal endpoints or using document transformers to reduce size.dotnet run --urls=http://localhost:5000curl http://localhost:5000/swagger/v1/swagger.json| File | Command / Code | Purpose |
|---|---|---|
| Program.cs | var builder = WebApplication.CreateBuilder(args); | Setting Up OpenAPI in ASP.NET Core 9/10 |
| Program.cs | builder.Services.AddOpenApi(options => | Customizing the OpenAPI Document |
| Program.cs | builder.Services.AddApiVersioning(options => | API Versioning with OpenAPI |
| WeatherController.cs | [ApiController] | Using Data Annotations and XML Comments |
| OpenApiTests.cs | public class OpenApiTests : IClassFixture | Testing OpenAPI Documentation with Integration Tests |
| Program.cs | if (app.Environment.IsDevelopment()) | Alternative Tools |
Key takeaways
Microsoft.AspNetCore.OpenApi.Common mistakes to avoid
3 patternsForgetting to add `AddOpenApi()` and `MapOpenApi()`
Mismatched security scheme (e.g., 'Bearer' vs 'bearer')
Not including XML comments in the OpenAPI document
Interview Questions on This Topic
How do you configure OpenAPI in ASP.NET Core 9/10?
builder.Services.AddOpenApi() to register services, and app.MapOpenApi() to serve the document. Optionally, add app.UseSwaggerUI() for interactive UI.Frequently Asked Questions
20+ years shipping production .NET services in enterprise systems. Written from production experience, not tutorials.
That's ASP.NET. Mark it forged?
6 min read · try the examples if you haven't