ASP.NET Core Web API · Cross-Cutting

Swagger and OpenAPI

Front-end and mobile developers should not have to read your C# to call the API. An OpenAPI document describes every endpoint, parameter and response in a standard JSON format — the basis for interactive docs, generated clients and contract tests. .NET 9 generates it for you.

Intermediate 6 min Lesson 22 of 37Examples target .NET 9.
Before you start:Controllers and Endpoints
0/37 in ASP.NET Core Web API
On this page · 6 min · Intermediate

In simple words#

OpenAPI is the printed menu of your API: every endpoint, input and output described, so others can order correctly — and Swagger UI lets them try it.

OpenAPI
Standard JSON description of an API.
Swagger UI
Interactive docs page.
Client generation
Create typed clients from the spec.

Learning objectives#

  • Generate an OpenAPI document with AddOpenApi and MapOpenApi
  • Describe responses with ProducesResponseType and summaries
  • Customise the document with transformers
  • Add an interactive UI (Scalar or Swagger UI)

Explanation#

.NET 9 includes Microsoft.AspNetCore.OpenApi: builder.Services.AddOpenApi() and app.MapOpenApi() serve the document at /openapi/v1.json. (Earlier templates used the Swashbuckle package; it still works but is no longer in the template.)

The generator reads routes, parameters, request bodies and return types. Add [ProducesResponseType<ProductDto>(200)] and [ProducesResponseType(404)] so error responses are documented too; ActionResult<T> and TypedResults help automatically.

[EndpointSummary] and [EndpointDescription] (or .WithSummary() for minimal APIs) give each operation a human-readable summary.

Document transformers edit the whole document: set the title, version, description, contact, and add security schemes such as Bearer JWT.

For an interactive UI, add Scalar (app.MapScalarApiReference()) or Swashbuckle’s Swagger UI pointing at /openapi/v1.json. Expose docs only where appropriate — often just in Development or behind auth.

Syntax#

C# Illustrative
builder.Services.AddOpenApi();
app.MapOpenApi();                                 // GET /openapi/v1.json


[HttpGet("{id:int}")]
[EndpointSummary("Get one product")]
[ProducesResponseType<ProductDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ProductDto> Get(int id) => …;

Code example: Generating and reading the OpenAPI document#

C#Program.cs Illustrative
// Program.cs
using System.Text.Json;


var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();                                    // keep the demo output clean
builder.Services.AddControllers();
builder.Services.AddOpenApi(options => options.AddDocumentTransformer((document, context, ct) =>
{
    document.Info.Title = "ShopSmart API";
    document.Info.Description = "Products, carts and orders for the ShopSmart store.";
    return Task.CompletedTask;
}));


var app = builder.Build();
app.MapOpenApi();                                                    // /openapi/v1.json
app.MapControllers();
app.Urls.Add("http://127.0.0.1:5222");
await app.StartAsync();


using var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:5222") };
var document = JsonDocument.Parse(await http.GetStringAsync("/openapi/v1.json")).RootElement;
Console.WriteLine(
quot;OpenAPI
{document.GetProperty("openapi")}: {document.GetProperty("info").GetProperty("title")} ({document.GetProperty("info").GetProperty("version")})");
foreach (var path in document.GetProperty("paths").EnumerateObject()) foreach (var operation in path.Value.EnumerateObject()) { var summary = operation.Value.TryGetProperty("summary", out var s) ? s.GetString() : ""; var codes = string.Join(", ", operation.Value.GetProperty("responses").EnumerateObject().Select(r => r.Name)); Console.WriteLine(
quot;
{operation.Name.ToUpperInvariant(),-6} {path.Name,-24} {summary,-22} responses: {codes}");
} Console.WriteLine(
quot;Schemas:
{string.Join(", ", document.GetProperty("components").GetProperty("schemas").EnumerateObject().Select(x => x.Name))}");
await app.StopAsync(); // Controllers/ProductsController.cs using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; public record ProductDto(int Id, string Name, decimal Price); public record CreateProductDto([Required] string Name, [Range(1, 1_000_000)] decimal Price); [ApiController] [Route("api/products")] public class ProductsController : ControllerBase { [HttpGet] [EndpointSummary("List products")] public IEnumerable<ProductDto> GetAll() => [new(1, "Wireless Mouse", 799m)]; [HttpGet("{id:int}")] [EndpointSummary("Get one product")] [ProducesResponseType<ProductDto>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult<ProductDto> Get(int id) => id == 1 ? new ProductDto(1, "Wireless Mouse", 799m) : NotFound(); [HttpPost] [EndpointSummary("Create a product")] [ProducesResponseType<ProductDto>(StatusCodes.Status201Created)] [ProducesResponseType<ValidationProblemDetails>(StatusCodes.Status400BadRequest)] public ActionResult<ProductDto> Create(CreateProductDto dto) => CreatedAtAction(nameof(Get), new { id = 2 }, new ProductDto(2, dto.Name, dto.Price)); }
Expected output
OpenAPI 3.0.1: ShopSmart API (1.0.0)
  GET    /api/products            List products          responses: 200
  POST   /api/products            Create a product       responses: 201, 400
  GET    /api/products/{id}       Get one product        responses: 200, 404
Schemas: CreateProductDto, ProblemDetails, ProductDto, ValidationProblemDetails

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. AddOpenApi registers the generator; the document transformer sets a friendly title and description.
  2. MapOpenApi serves the document at /openapi/v1.json, which the demo reads like any client would.
  3. Each operation lists its summary and documented response codes, including 404 and 400.
  4. Request and response types become reusable schemas (ProductDto, CreateProductDto, ValidationProblemDetails…).
  5. Tools such as Scalar, Swagger UI, NSwag or Kiota turn this document into interactive docs and typed clients.

Real-world use case#

ShopSmart’s Android and iOS teams regenerate their API clients from /openapi/v1.json in CI. When a pull request changes a response type, the generated client stops compiling — before the app ever ships.

Common mistakes#

Undocumented error responses

Clients need to know about 400, 404 and 409 just as much as 200. Add ProducesResponseType for each.

Returning IActionResult without metadata

The generator cannot know the success type. Use ActionResult<T>, TypedResults or ProducesResponseType<T>.

Public docs for internal endpoints

Interactive docs for admin endpoints on the public internet help attackers. Restrict them.

Best practices#

  • AddOpenApi + MapOpenApi (.NET 9+)
  • Summaries and all response types documented
  • Transformers for title, version and security
  • Generate clients from the document

Remember this#

/openapi/v1.json is generated from your endpoints.

ProducesResponseType for every status a client may see.

Scalar or Swagger UI reads the same document.

Practice exercise#

Your task: Add the Scalar interactive UI in Development only, available at /scalar/v1.

Need a hint?
dotnet add package Scalar.AspNetCore; then if (app.Environment.IsDevelopment()) { app.MapOpenApi(); app.MapScalarApiReference(); }
Show solution
C# Illustrative
// Program.cs
using Scalar.AspNetCore;


var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var app = builder.Build();


if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();               // /openapi/v1.json
    app.MapScalarApiReference();    // /scalar/v1
}


app.MapControllers();
app.Run();

Mini quiz#

Swagger and OpenAPI quizMultiple choice Question 1 of 3

Where does MapOpenApi serve the default document in .NET 9?

Score so far: 0/0

Want more? Practise all ASP.NET Core Web API MCQs · ASP.NET Core Web API interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.