Janzen.Pagination.AspNetCore 10.1.0

Prefix Reserved
dotnet add package Janzen.Pagination.AspNetCore --version 10.1.0
                    
NuGet\Install-Package Janzen.Pagination.AspNetCore -Version 10.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Janzen.Pagination.AspNetCore" Version="10.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Janzen.Pagination.AspNetCore" Version="10.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Janzen.Pagination.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Janzen.Pagination.AspNetCore --version 10.1.0
                    
#r "nuget: Janzen.Pagination.AspNetCore, 10.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Janzen.Pagination.AspNetCore@10.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Janzen.Pagination.AspNetCore&version=10.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Janzen.Pagination.AspNetCore&version=10.1.0
                    
Install as a Cake Tool

Janzen.Pagination.AspNetCore

ASP.NET Core integration for Janzen.Pagination.

Wires the pagination engine into the web pipeline:

  • Query-string model binding — bind PaginateQuery straight from the request (?page=&limit=&sortBy=&search=&filter.<field>=$op:value).
  • ProblemDetails error handling — invalid queries surface as consistent 400 responses.
  • Pagination linksfirst / previous / next / last built from the current request.
  • OpenAPI metadata — documents the pagination query parameters on annotated endpoints; per-field badges (configured via .ShowBadge(...)) render as chips in the API reference UI, colorable via your custom CSS. Fields gated with .When(...) stay documented (widest surface) and are enforced at runtime.

The contract it binds is borrowed from nestjs-paginate (MIT) — the same parameters, operator names and response envelope.

Install

dotnet add package Janzen.Pagination.AspNetCore

Requires Janzen.Pagination.EntityFrameworkCore (referenced transitively).

Usage

// Program.cs — register the query-string model binder and the 400 ProblemDetails filter.
services.AddPagination(pagination => pagination.AddAspNetCore());

// Register the OpenAPI operation transformer on your document so the pagination query
// parameters are documented on endpoints annotated with [PaginatedQuery<TConfigProvider>].
// (The transformer is a normal IOpenApiOperationTransformer — your app owns the document name.)
using Janzen.Pagination.AspNetCore.OpenApi;
services.AddOpenApi(options => options.AddOperationTransformer<PaginatedQueryOperationTransformer>());

// Controller — PaginateQuery is bound from the query string. MVC binds the CancellationToken to
// HttpContext.RequestAborted for free; pass it so a disconnected client stops the database work.
[HttpGet]
[PaginatedQuery<ProductConfigProvider>]
public Task<PaginatedResponse<ProductDto>> Get([FromQuery] PaginateQuery request, CancellationToken ct) =>
    _dbContext.Products.PaginateAsync<Product, ProductDto>(request, _config, HttpContext.Request, ct);

Minimal API

app.MapGet("/products", async (HttpContext http, AppDbContext db, CancellationToken ct) =>
        await db.Products.PaginateAsync<Product, ProductDto>(http.Request.ToPaginateQuery(), config, http.Request, ct))
   .WithPagination<ProductConfigProvider>();

WithPagination<TConfigProvider>() attaches the OpenAPI pagination parameters and the documented 400 response, and maps PaginateQueryException to a 400 Problem Details via an endpoint filter. Request.ToPaginateQuery() builds the PaginateQuery from the query string.

Passing the HttpRequest to a Paginate*Async call is what fills response.Links with first/previous/next/last and current, built from the current request: path-relative — including the app's UsePathBase prefix — with every other query parameter preserved. An absent link (previous on page 1, next on the last page) is serialized as null rather than dropped, so the shape does not change per page; current echoes the request and is always there. Omit the HttpRequest and Links is null as a whole. For the RFC 8288 header as well (a null Links writes no header):

var page = await db.Products.PaginateAsync<Product, ProductDto>(request, config, this.Request, ct);
this.Response.AddPaginationLinkHeader(page.Links);

What the client gets back

{
  "items": [ { "id": "7f3c…", "name": "Widget Pro", "price": 249.00 } ],
  "meta":  { "totalItems": 26, "itemCount": 1, "itemsPerPage": 25, "totalPages": 2, "currentPage": 2,
             "sortBy": ["name:ASC"], "search": null, "searchBy": [],
             "filter": { "status": ["$eq:Active"] },
             "hasPreviousPage": true, "hasNextPage": false },
  "links": { "first": "/products?limit=25&filter.status=%24eq%3AActive&page=1",
             "previous": "/products?limit=25&filter.status=%24eq%3AActive&page=1",
             "next": null,
             "last": "/products?limit=25&filter.status=%24eq%3AActive&page=2",
             "current": "/products?limit=25&filter.status=%24eq%3AActive&page=2" }
}

meta echoes the effective request, not the raw one: this response reports "sortBy": ["name:ASC"] even though the client sent no sortBy, because that is where DefaultSortBy landed — which is exactly what a grid header needs to draw its arrow. Every key is always present; null, [] and {} carry the absent cases. See Response contract.

Errors

Any invalid query becomes 400 Bad Request with title: "Invalid query" and the specific message as detail — via PaginateExceptionFilter for controllers (registered by AddAspNetCore()) or PaginateExceptionEndpointFilter for Minimal APIs (registered by WithPagination<T>()). Both answer application/problem+json and each is enriched exactly once by its own framework half, so an AddProblemDetails customisation applies on either. The members match where the framework halves do: type, title, status, detail and code come from this library on both legs, while traceId is the framework's and reaches a Minimal API response only when the app registered AddProblemDetails(). No per-action try/catch needed.

The payload carries a code member — the PaginateQueryException.Code member name, such as SortFieldNotConfigured — so a client can branch on the cause instead of matching the detail prose.

Unknown query parameters are ignored, so clients keep their own tracking parameters; page and limit are validated.

Documentation

Debugging

The package ships embedded PDBs with Source Link, so a debugger steps straight into these sources at the exact commit the version was built from. Nothing to configure: no symbol server, no separate symbol download, and it works offline.

License

MIT © Lubos Jansky

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.