The Kladde’s API has by now become a contract with many clauses: addresses, parameters, the content of a request, status codes, the form of
every response and of the error. So far, this contract exists only in the code and in the curl output of your pull requests. Whoever
writes a program that calls the Kladde would have to read the code. For this, there is OpenAPI: a description of an HTTP API in JSON or
YAML that people can read and tools can process. From an OpenAPI document, you can generate clients, send test requests and build
documentation pages.
ASP.NET Core writes this document itself, from the endpoints you have anyway. In .NET 10, it produces OpenAPI version 3.1 by default. In this lesson, you generate and read the document, describe the endpoints so that it is correct, and learn two rules for designing an API: how a long list is split into pages and which changes break existing callers. At the end, the Kladde’s document is in the repository, and the contacts come page by page on request.
Generating the document
In k2-practice/2-3, create the file description.cs, a small API for notes:
// description.cs: an API that describes itself
#:sdk Microsoft.NET.Sdk.Web
#:package Microsoft.AspNetCore.OpenApi@10.0.14
#:property PublishAot=false
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http.HttpResults;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.SetMinimumLevel(LogLevel.Warning);
builder.Services.AddProblemDetails();
builder.Services.AddValidation();
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
List<Note> notes = [new(1, "Service the server"), new(2, "Write the quote")];
RouteGroupBuilder group = app.MapGroup("/api/notes").WithTags("Notes");
group.MapGet("/{id:int}", Results<Ok<Note>, ProblemHttpResult> (int id) =>
notes.FirstOrDefault(n => n.Id == id) is Note note
? TypedResults.Ok(note)
: TypedResults.Problem(statusCode: StatusCodes.Status404NotFound))
.WithName("GetNote")
.WithSummary("Reads one note.");
group.MapPost("/", (NoteRequest request) =>
{
Note note = new(notes.Count + 1, request.Text!);
notes.Add(note);
return TypedResults.Created($"/api/notes/{note.Id}", note);
})
.WithName("CreateNote")
.WithSummary("Creates a note.");
app.Run();
public record Note(int Id, string Text);
public record NoteRequest([Required, MaxLength(200)] string? Text, int Priority = 2);
Three lines are new:
#:package Microsoft.AspNetCore.OpenApi@10.0.14brings in the package that generates the description. It isn’t part of the framework and is added in a project withdotnet package add. The version is the one that was current when this lesson was written.builder.Services.AddOpenApi()registers the services that generate the document. It is calledv1.app.MapOpenApi()serves it at/openapi/v1.json, here only in theDevelopmentenvironment.
Start the app in development. --environment Development after the two dashes sets the environment on the command line, like the keys in
lesson 2.3.4:
dotnet run description.cs -- --environment Development
In the second terminal, fetch the document:
curl http://localhost:5000/openapi/v1.json
It is a good hundred lines long. Here is the part that describes GET /api/notes/{id}:
"paths": {
"/api/notes/{id}": {
"get": {
"tags": [
"Notes"
],
"summary": "Reads one note.",
"operationId": "GetNote",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Note"
}
}
}
}
}
}
},
Stop the app with Ctrl+C and start it again without -- --environment Development. Now /openapi/v1.json answers with 404 Not Found.
That is intended: The document names every address and every field of the API, and whoever uses the Kladde in production doesn’t need
it. Where the document goes instead is shown in the section about breaking changes further down.