ASP.NET Core 10 APIs · Lesson 8

OpenAPI as an observable contract

Generate a real document and compare it with handler behavior.

External .NET 10 SDK 10.0.403; ASP.NET Core server on your own desktop. Kestrel does not run in the SharpRune browser console.

Read or download the complete SDK project, then follow its setup on your computer. The separate C# companion practice below runs in SharpRune’s .NET 9 console environment.

A generated API description is a contract artifact

OpenAPI gives tools a structured description of HTTP operations, parameters and representations. It does not start a server on its own or guarantee that every described branch works. The external project registers first-party ASP.NET Core 10 OpenAPI services and exposes the generated document only in Development. Its package reference is explicitly pinned to the verified version. The portable companion instead compares two sets of outcome names. That small check illustrates description drift without pretending to generate OpenAPI, host documentation or parse the real server's schema inside the embedded console.

Observe a matching description and an intentional omission

The console begins with documented and implemented outcome sets containing found and missing. Set equality prints matched. It then removes missing from the documented set and prints missing outcome. On the real server, a typed endpoint describes successful item lookup and absence, and /openapi/v1.json exposes those alternatives under the item path. A request for identifier one returns the item while nine returns 404. The server's generated document and its real outcomes are complementary observations; the console's set example merely explains why a description can drift when an alternative is omitted.

Typed results contribute useful but bounded metadata

The endpoint names its operation and supplies a summary while its typed union exposes 200 and 404 alternatives. The route parameter is an integer, so the generated document can describe that input. Metadata should remain aligned with actual behavior, especially when adding new failure branches or representations. A generated document cannot discover a business guarantee that the code never expresses, nor can it prove that an external dependency fulfills its contract. Inspect the paths and schemas clients will actually consume rather than assuming that a successful AddOpenApi call implies every important policy is documented correctly.

A document endpoint is separate from a visual UI

MapOpenApi exposes the serialized document; this project adds no interactive Swagger UI or other documentation frontend. The Development guard is an intentional exposure choice, so requesting the same document in Production should not be treated as proof of a broken item endpoint. Decide how a deployed system makes its contract available and whether that artifact belongs behind a specific access boundary. Avoid attaching an unreviewed UI package merely because a browser displays JSON. A valid document can be downloaded, checked into an integration workflow or consumed by tooling without any UI running inside the application.

Practice: compare a new response branch with the document

Add a separately specified invalid-identifier result to the item endpoint, update its typed alternatives and rebuild. Inspect whether the generated responses now include the intended status and representation. Then request that branch directly. Keep a copy of the previous document and identify the precise contract change instead of comparing raw JSON formatting. The hint is to inspect operation responses and referenced schemas structurally. A branch that returns the expected status at runtime but is missing from the generated description is a different failure from a document describing a branch the handler never executes.

Practice: verify environment-specific availability

Run the same compiled project once in Development and once in Production on separate owned loopback ports. Predict which run exposes /openapi/v1.json and whether both should still serve /items/1. Stop each owned process after checking it. Then decide how a client-generation workflow would obtain the document without depending on a production UI. This exercise separates documentation exposure from application functionality. Do not change route behavior while changing the environment; otherwise a missing item response could be incorrectly attributed to the OpenAPI guard instead of an independent configuration difference.

Use generated documents and request tests together

A useful review examines operation identity, parameter source and type, response alternatives and schema shape, then checks representative requests against those expectations. The supplied project establishes a small real example of that workflow with no database or remote service. Preserve explicit package versions when reproducing the lab, and inspect document changes when upgrading the framework or changing endpoint types. The console remains useful for the general idea of contract drift, but only the external project's generated artifact represents this API. Keep that distinction visible when claiming what was compiled, described and actually exercised through HTTP.

  • Inspect generated document structure and real response branches independently.
  • MapOpenApi exposes a document; a visual documentation UI is a separate component.
External .NET 10 SDK 10.0.403; ASP.NET Core server on your own desktop. Kestrel does not run in the SharpRune browser console.

OpenAPI as an observable contract — real ASP.NET Core 10 project

Set up the SDK project

  1. Save all files into one new empty directory, preserving their relative paths. Install the official .NET 10 SDK if it is not already available.
  2. From that directory run: dotnet restore WebLab.csproj --source https://api.nuget.org/v3/index.json
  3. Start only on loopback: ASPNETCORE_ENVIRONMENT=Development dotnet run --project WebLab.csproj --no-launch-profile --urls http://127.0.0.1:5080
  4. Use another terminal for the requests below, then press Ctrl+C in this project’s terminal to stop its server.
  5. GET /openapi/v1.json; inspect paths, parameters and responses. Request /items/1 and /items/9 separately.

Download complete SDK project ↗

Project files

WebLab.csprojDownload file ↗
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.14" />
  </ItemGroup>
</Project>
global.jsonDownload file ↗
{
  "sdk": {
    "version": "10.0.403",
    "rollForward": "latestPatch"
  }
}
Program.csDownload file ↗
using Microsoft.AspNetCore.Http.HttpResults;
var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); var app = builder.Build();
if (app.Environment.IsDevelopment()) app.MapOpenApi();
app.MapGet("/items/{id:int}", Results<Ok<Item>, NotFound> (int id) => id == 1 ? TypedResults.Ok(new Item(1, "rune")) : TypedResults.NotFound()).WithName("GetItem").WithSummary("Find one classroom catalog item");
app.Run();
public sealed record Item(int Id, string Name);

Expected result

In Development, GET /openapi/v1.json contains /items/{id} with integer id and 200/404 responses. Actual /items/1 returns JSON; /items/9 returns 404. No Swagger UI is installed.

Use the listed SDK and dependencies for this project. Open C# practice runs the separate portable exercise.

C# companion practice

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

var documented = new HashSet<string>(new[] { "found", "missing" }, StringComparer.Ordinal); var implemented = new[] { "found", "missing" };
Console.WriteLine(documented.SetEquals(implemented) ? "matched" : "drift"); documented.Remove("missing"); Console.WriteLine(documented.SetEquals(implemented) ? "matched" : "missing outcome");
Open C# practice

C# practice output

matched
missing outcome
Try this
Read the generated paths and response statuses, then verify those branches through real requests.

Further reading