Wiaoj.Pagination.OpenApi
0.2.0-alpha.3
dotnet add package Wiaoj.Pagination.OpenApi --version 0.2.0-alpha.3
NuGet\Install-Package Wiaoj.Pagination.OpenApi -Version 0.2.0-alpha.3
<PackageReference Include="Wiaoj.Pagination.OpenApi" Version="0.2.0-alpha.3" />
<PackageVersion Include="Wiaoj.Pagination.OpenApi" Version="0.2.0-alpha.3" />
<PackageReference Include="Wiaoj.Pagination.OpenApi" />
paket add Wiaoj.Pagination.OpenApi --version 0.2.0-alpha.3
#r "nuget: Wiaoj.Pagination.OpenApi, 0.2.0-alpha.3"
#:package Wiaoj.Pagination.OpenApi@0.2.0-alpha.3
#addin nuget:?package=Wiaoj.Pagination.OpenApi&version=0.2.0-alpha.3&prerelease
#tool nuget:?package=Wiaoj.Pagination.OpenApi&version=0.2.0-alpha.3&prerelease
Wiaoj.Pagination.OpenApi
Makes pagination visible in generated OpenAPI documents — both what WithPagination() does to an endpoint, and the shape of what it returns.
Installation
dotnet add package Wiaoj.Pagination.OpenApi
builder.Services.AddOpenApi(options => options.AddWiaojPagination());
That is the whole setup. Endpoints without WithPagination() are left untouched, so it is safe to add once for the entire document.
The problem it solves
WithPagination() installs an endpoint filter. The filter writes RFC 8288 Link headers, computes an ETag, and answers 304 Not Modified when the client sends a matching If-None-Match — none of which appears anywhere in the handler's signature.
So a document generated without this package describes an endpoint that returns 200 and sets no headers. That is not the endpoint that exists. A client generated from it has no way to follow rel="next", because as far as the document is concerned there is no Link header to read.
Neither is the result type described. PagedResult<T> and CursorResult<T> serialise through hand-written converters, and their CLR properties are an EquatableArray<T> and a CursorToken rather than an array and a string — nothing a schema generator reading properties can make sense of. Its answer to a type it cannot read is an empty schema, and openapi-typescript renders that as:
CursorResultOfProductDto: unknown;
The responses are correct; only the document is silent about them. It surfaces one step out, in the generated client, and the rational move for whoever hits it is to go back to whichever paging style is described — which is the wrong reason to choose a pagination strategy.
What it adds
PagedResult<T> / CursorResult<T> schemas |
{ items: T[], metadata }, with items referencing T's own schema |
PageMetadata / CursorMetadata schemas |
shared components, referenced rather than inlined per endpoint |
Link response header |
on every 2xx response, when link headers are enabled |
ETag response header |
on every 2xx response, when ETag evaluation is enabled |
304 response |
when ETag evaluation is enabled |
| Paging query parameters | page / size, or cursor / limit / direction |
A cursor is described as a nullable string, not as the struct it is. A caller sends back what it was given; the inside of a cursor is not part of the contract, and describing it would invite clients to read it.
The parameter set is chosen from the endpoint's declared response type rather than guessed: PagedResult<T> means offset paging, CursorResult<T> means keyset. An endpoint returning neither gets nothing documented — no parameters, no Link, no ETag, no 304 — because the pagination filter only acts on a result it recognises as a page, and a document claiming those headers would describe an endpoint that does not send them.
An endpoint whose response carries the page inside an envelope says so with WithPagination<TResponse>(response => response.Metadata). That declares its style, so it is documented as offset or keyset from the declaration rather than inferred.
A handler returning IResult has no response type to read. WithPagination(PaginationStyle.Cursor) states the style for it. The statement never overrides a response type the document does know: a Produces<PagedResult<T>>() on an endpoint declared Cursor fails document generation instead of producing a document that describes parameters the endpoint does not accept.
Whether Link and ETag are documented follows the settings actually in effect — services.AddPagination(...), refined by the endpoint's own WithPagination(options => ...). The document resolves them through the same method, on the same metadata, the filter uses at run time, so it cannot advertise an ETag the application turned off.
Parameters the document already carries are left alone. A handler taking [AsParameters] PageRequest already has page and size described by ASP.NET Core, and adding them twice produces an invalid document. A handler taking CursorParameters has nothing described — a type that binds itself contributes no parameters to the document — so this is where cursor, limit and direction come from.
A handler taking a bare CursorRequest or PageRequest — without [AsParameters] — is a different endpoint: it binds the whole request from one query value, in the compact format cursor:limit:direction or page:size. The document is given that grammar, and the separate parameters are not added, because that endpoint does not accept them.
Defaults and bounds come from the types themselves — CursorRequest.DefaultLimit, CursorRequest.MaxLimit, PageRequest.DefaultSize, PageRequest.MaxSize — so the document cannot drift from the values the code actually enforces.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.OpenApi (>= 10.0.12)
- Wiaoj.Pagination.AspNetCore (>= 0.2.0-alpha.3)
- Wiaoj.Preconditions (>= 0.2.0-alpha.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.0-alpha.3 | 47 | 9/24/2026 |
| 0.2.0-alpha.2 | 45 | 9/24/2026 |
| 0.2.0-alpha.1 | 47 | 9/24/2026 |
| 0.1.0-alpha.9 | 49 | 9/21/2026 |
| 0.1.0-alpha.8 | 48 | 9/21/2026 |
| 0.1.0-alpha.7 | 48 | 9/18/2026 |
| 0.1.0-alpha.6 | 46 | 9/16/2026 |
| 0.1.0-alpha.5 | 51 | 9/16/2026 |
| 0.1.0-alpha.4 | 50 | 9/16/2026 |
| 0.1.0-alpha.3 | 48 | 9/15/2026 |
| 0.1.0-alpha.2 | 51 | 9/15/2026 |
| 0.1.0-alpha.1 | 70 | 9/14/2026 |
| 0.0.1-alpha.112-preview | 50 | 9/13/2026 |
| 0.0.1-alpha.111-preview | 49 | 9/13/2026 |
| 0.0.1-alpha.110-preview | 53 | 9/12/2026 |
| 0.0.1-alpha.109-preview | 75 | 9/11/2026 |
| 0.0.1-alpha.108-preview | 73 | 9/8/2026 |
| 0.0.1-alpha.107-preview | 70 | 9/8/2026 |
| 0.0.1-alpha.106-preview | 63 | 9/8/2026 |
| 0.0.1-alpha.105-preview | 60 | 9/8/2026 |