MinimalOpenAPI is a contract-first OpenAPI framework for ASP.NET Core Minimal APIs.
Pre-release — currently targeting 1.0. APIs are subject to change until a stable release is tagged. See release maturity for details.
In a typical ASP.NET Core Minimal API project you define routes in C# and then generate an OpenAPI document from that code (code-first). The OpenAPI document is a by-product and can drift from the actual implementation.
MinimalOpenAPI flips that: you author the OpenAPI document first, and the library generates all the C# scaffolding from it at build time. The document is the single source of truth — the generated code is always in sync.
| Code-first | Contract-first (MinimalOpenAPI) | |
|---|---|---|
| Source of truth | C# code | openapi.yaml / openapi.json |
| OpenAPI document | Generated (can drift) | Authored; drives the code |
| C# scaffolding | Manual | Generated |
| Client compatibility | Loose | Enforced by the contract |
This model is useful when:
- the API contract is designed independently (e.g. with Stoplight or Swagger Editor)
- the contract must be shared with client teams before implementation starts
- the contract is versioned separately from the server code
openapi.yaml ──► [MinimalOpenAPI] ──► Generated C#
openapi.json ──► (build time) │
├─ DTO records
├─ Abstract handler base classes
├─ DI registration
└─ Endpoint mapping
The Roslyn source generator reads the OpenAPI file at build time and emits:
- DTO records — one
sealed recordpercomponents/schemasobject. - Handler base classes — one abstract
<OperationId>EndpointBaseper operation with a strongly-typedHandleAsyncsignature. - DI registration — a generated
AddGeneratedEndpointsextension and a[ModuleInitializer]that wires everything up automatically. - Endpoint mapping — a generated
MapEndpointsthat registers all routes.
You only write the business logic.
- .NET 10 is required at runtime. The
MinimalOpenAPIpackage targetsnet10.0for its runtime services andnetstandard2.0for the Roslyn analyzer host. - ASP.NET Core (via
Microsoft.AspNetCore.Appframework reference) is required in the consuming project.
| Package | NuGet | Description |
|---|---|---|
MinimalOpenAPI |
The only package you need. Bundles the Roslyn source generator and the ASP.NET Core runtime services (AddMinimalOpenApi, MapMinimalOpenApiEndpoints). |
The MinimalOpenAPI.Abstractions, MinimalOpenAPI.Parser.Yaml, and
MinimalOpenAPI.Parser.Json projects are internal implementation details — their
DLLs are bundled inside the package and are not published separately.
Pre-release packages are published to the GitHub Packages NuGet feed on manual execution of the Publish workflow:
https://nuget.pkg.github.com/Kralizek/index.json
Add the MinimalOpenAPI package to your ASP.NET Core project:
dotnet add package MinimalOpenAPIOr manually in your .csproj:
<PackageReference Include="MinimalOpenAPI" Version="1.0.0-beta.1" />1 — Add the package and reference your OpenAPI spec file:
<!-- MyApi.csproj -->
<ItemGroup>
<PackageReference Include="MinimalOpenAPI" Version="1.0.0-alpha" />
<OpenApi Include="openapi.yaml" /> <!-- or openapi.json -->
</ItemGroup>2 — Register services and map endpoints in Program.cs:
using MinimalOpenAPI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMinimalOpenApi();
var app = builder.Build();
app.MapMinimalOpenApiEndpoints();
app.Run();3 — Define a minimal OpenAPI spec (openapi.yaml):
openapi: "3.0.0"
info:
title: Items API
version: "1.0.0"
paths:
/items/{id}:
get:
operationId: getItem
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
"404":
description: Not found
components:
schemas:
Item:
type: object
required: [id, name]
properties:
id:
type: string
format: uuid
name:
type: string4 — Implement the generated handler base class:
// GetItemEndpoint.cs
using Microsoft.AspNetCore.Http.HttpResults;
using MyApi.Contracts;
using MyApi.Endpoints;
public sealed class GetItemEndpoint(IItemRepository repo) : GetItemEndpointBase
{
public override async Task<Results<Ok<Item>, NotFound>> HandleAsync(
Guid id,
CancellationToken cancellationToken)
{
var item = await repo.FindAsync(id, cancellationToken);
return item is null ? TypedResults.NotFound() : TypedResults.Ok(item);
}
}That's it. No manual route registration, no manual DI wiring.
| Feature | Notes |
|---|---|
| YAML and JSON specs | <OpenApi Include="openapi.yaml" /> or openapi.json |
| OpenAPI 3.0 and 3.1 | Both versions normalised to the same internal model |
| Multiple spec files | Each spec gets its own {RootNamespace}.{SpecName} sub-namespace |
| DTO records | One sealed record per components/schemas object |
| Enum types | enum schemas produce C# enum with [JsonStringEnumConverter] |
| Inline object schemas | Nested object properties produce named sibling records |
additionalProperties |
Maps to Dictionary<string, T>; inline object value types get a generated record |
| Validation attributes | minLength, maxLength, pattern, minimum, maximum, minItems, maxItems → DataAnnotations |
format: date |
Maps to DateOnly |
| Path parameters | Typed with route constraints ({id:guid}, {page:int}, …) |
| Query / header / cookie params | Grouped into a Parameters record with [AsParameters] |
| Spec publishing | <OpenApi Publish="true" /> copies spec to build and publish output |
| HTTP schema serving | MapOpenApiSchemas() serves GET /.openapi/schemas/{version}/{name}.{ext} |
| Endpoint customizers | Optional <OperationId>EndpointRegistration base for per-route metadata |
See the sample app for a complete end-to-end example covering all of these.
Mark the spec for publishing in the project file:
<ItemGroup>
<OpenApi Include="openapi.yaml" Publish="true" />
</ItemGroup>Then serve it in Program.cs:
app.MapMinimalOpenApiEndpoints();
app.MapOpenApiSchemas(); // GET /.openapi/schemas/{version}/{name}.{ext}MapOpenApiSchemas extracts the info.version field from each file and registers one endpoint per spec. It returns a RouteGroupBuilder for further configuration:
app.MapOpenApiSchemas().RequireAuthorization("InternalOnly");An OpenAPI spec can be shipped in a separate NuGet "contracts" package (the same pattern used by gRPC .proto files) and the consuming project does not need an <OpenApi> item of its own. See docs/architecture.md §5.1 for details.
Each <OpenApi> item generates code in its own sub-namespace, preventing type-name collisions across specs:
<ItemGroup>
<OpenApi Include="orders.yaml" />
<OpenApi Include="payments.yaml" Namespace="Payment" />
</ItemGroup>Generated namespaces:
{RootNamespace}.Orders.Contracts/{RootNamespace}.Orders.Endpoints{RootNamespace}.Payment.Contracts/{RootNamespace}.Payment.Endpoints
- No Swashbuckle/Scalar integration. MinimalOpenAPI does not generate an OpenAPI document at runtime. To serve the original spec file as a static HTTP endpoint use
<OpenApi Publish="true" />andMapOpenApiSchemas(). - Schema composition not supported.
allOf,oneOf, andanyOfare not yet implemented. - No runtime validation. Validation attributes on generated properties are informational. ASP.NET Core Minimal APIs do not run
DataAnnotationsvalidation automatically. - No code-first path. Use Swashbuckle, NSwag, or
Microsoft.AspNetCore.OpenApiif you want to generate an OpenAPI document from C# code. - OpenAPI 2.0 (Swagger) not supported.
Warning MOA001 — no handler implementation found
The generator emits a warning when it cannot find a class that inherits from a generated <OperationId>EndpointBase. The app will still compile, but HandleAsync will throw NotImplementedException at runtime. Add a concrete handler class:
public sealed class GetItemEndpoint : GetItemEndpointBase
{
public override async Task<Results<Ok<Item>, NotFound>> HandleAsync(
Guid id, CancellationToken cancellationToken)
=> TypedResults.NotFound();
}Build error MOA002 — multiple handler implementations
Only one class may inherit from a given generated base. Remove or consolidate the duplicate.
Build error MOA003 — multiple customizer implementations
At most one class may inherit from a given generated <OperationId>EndpointRegistration base. Remove or consolidate the duplicate.
Build error MOA004 — OpenAPI file could not be parsed
Check the spec file for YAML/JSON syntax errors. Validate it with a tool like the Swagger Editor before referencing it in the project.
Build error MOA005 — unrecognised file extension
Only .yaml, .yml, and .json are supported. Rename the file or use the correct extension in the <OpenApi> item.
Warning MOA006 — unknown OpenAPI version
The openapi field is absent or not recognised as a 3.0.x or 3.1.x version string. Code is still generated, but behaviour may be incorrect. Add or correct the openapi field at the top of the spec file (e.g. openapi: "3.1.0").
Handler HandleAsync throws NotImplementedException at runtime
The base class throws NotImplementedException by default. Make sure your concrete handler overrides HandleAsync and does not call base.HandleAsync(...).
MapMinimalOpenApiEndpoints returns an empty route group
This happens when the source generator did not run (e.g. the <OpenApi> item is missing from the project file, or the spec file path is wrong). Verify that the <OpenApi> item points to an existing file and that a build was performed after adding it.
MinimalOpenAPI is currently in pre-release. The version scheme follows Semantic Versioning:
1.0.0-alpha— initial functionality, internal testing only1.0.0-beta.*— public pre-release; APIs may still change1.0.0-rc.*— release candidate; no planned breaking changes1.0.0— stable; breaking changes only in major versions
Until 1.0.0 is tagged, minor version bumps may include breaking changes. Pin to a specific version in production use.
src/
MinimalOpenAPI/ ← MinimalOpenAPI NuGet package (generator + runtime services)
MinimalOpenAPI.Abstractions/ ← document model & parser contracts
MinimalOpenAPI.Parser.Yaml/ ← YAML parser implementation
MinimalOpenAPI.Parser.Json/ ← JSON parser implementation
sample/
MinimalOpenAPI.Sample.Api/ ← end-to-end Todo CRUD example
MinimalOpenAPI.SmokeTest.Api/ ← minimal consumer built against the packed NuGet artifact
tests/
MinimalOpenAPI.Generator.Tests/
MinimalOpenAPI.Runtime.Tests/
MinimalOpenAPI.IntegrationTests/
docs/
architecture.md ← internals, design decisions, extensibility
releasing.md ← versioning and release process
schema-feature-roadmap.md ← OpenAPI schema feature coverage and backlog
consumer-agents.md ← guide for coding agents integrating this library
For a deep-dive into the design, architecture, and internals see docs/architecture.md.
For guidance on how coding agents (e.g. GitHub Copilot) should use this library in consumer projects, see docs/consumer-agents.md.
Contributions are welcome. Please open an issue first to discuss proposed changes.
- Fork the repository.
- Create a feature branch (
git checkout -b feature/my-change). - Commit your changes — the CI pipeline enforces a warning-free build (
--warnaserror) and runs all unit and integration tests. - Open a pull request against
master.
See CONTRIBUTING.md for development setup details.