Repository navigation
Minimal API Integration
The minimal-API integration is gated by #if NET7_0_OR_GREATER. If you target net7.0, net8.0 or net9.0, the PagedListResult.Web package gives you a complete, idiomatic minimal-API surface that mirrors the MVC pipeline.
The types on this page live in the
RzR.ResultMessage.Pagination.AspNetCore.Querynamespace. This namespace was renamed in v5 (previouslyRzR.ResultMessage.Pagination.AspNetCore.MinimalApi) — see Migration Guide if you are upgrading.
| Component | Purpose |
|---|---|
PagedQuery<TEntity> |
Bindable wrapper that extends PagedRequest and accepts the same query string the MVC binder accepts. |
PagedQueryWithFilters<TEntity> |
Bindable wrapper that extends PageRequestWithFilters (adds Filters). |
MinimalApiQueryBinder.Bind<TEntity>(target, ctx) |
Shared binding logic; reused by both wrappers. Internal implementation detail — the type is internal and is not intended to be called directly. |
PagedResultEndpointFilter |
IEndpointFilter that emits the standard pagination headers. |
WithPagedResult() / AddPagedResultEndpointFilter<TBuilder>()
|
Attach the endpoint filter. |
ToPagedHttpResult(this IPagedResult<T>, HttpContext?) |
Adapt the paged envelope to a minimal-API IResult. |
Both types implement the BindAsync(HttpContext) pattern recognized by minimal APIs, so you can take them directly in an endpoint delegate:
app.MapGet("/products",
async (PagedQueryWithFilters<Product> q, AppDbContext db, HttpContext ctx, CancellationToken ct) =>
{
if (!q.IsValid)
return Results.ValidationProblem(
q.Errors.ToDictionary(e => e.Key, e => new[] { e.Value }));
var paged = await db.Products.AsNoTracking()
.GetPagedWithFiltersAsync(q, cancellationToken: ct);
return paged.ToPagedHttpResult(ctx);
})
.WithPagedResult();Both wrappers expose:
| Member | Type | Description |
|---|---|---|
IsValid |
bool |
true when Errors is empty. |
Errors |
IDictionary<string, string> |
Per-key binding error messages, keyed case-insensitively. Same filter[i] convention as the MVC binder. |
Both members come from IPagedQueryValidation (RzR.ResultMessage.Pagination.AspNetCore.Abstractions), the shared abstraction both wrappers implement.
Also usable in MVC. As of v5 these same wrapper types can be bound as an MVC action parameter via [FromPagedQuery]. IPagedQueryValidation is what makes IsValid / Errors behave identically in both hosts. See MVC Integration for the pattern and when to prefer it over the plain PagedRequest types.
Internally BindAsync resolves PagedListResultWebOptions and the IPageableMetadataRegistry from HttpContext.RequestServices, looks up the PageableMetadata registered for TEntity, and calls the internal MinimalApiQueryBinder.Bind<TEntity>(this, ctx) (not a public API — shown here only to explain the binding flow). The wrapper is always returned (never null) so the endpoint can decide whether to fail with ValidationProblem or proceed.
Both extension methods attach PagedResultEndpointFilter to the endpoint or route group:
app.MapGroup("/api")
.WithPagedResult()
.MapGet("/products", ...);
// equivalent
app.MapGroup("/api")
.AddPagedResultEndpointFilter()
.MapGet("/products", ...);The filter inspects the endpoint's return value:
- If it is
null, returnsResults.NoContent(). - If it implements
IPagedResultdirectly, callsPagedResponseHeaderWriter.TryApply. - Otherwise it tries to unwrap a
Valueproperty viaPagedAccessors(coversOk(paged)and similarIResultwrappers) and applies the writer to the unwrapped value.
The filter does not reshape the body. It only writes headers. Use ToPagedHttpResult if you need the success body to keep the full envelope.
This adapter sits between your endpoint code and Results.Ok / the upstream ResultToHttpResult.ToHttpResult<T>:
| Input | Output |
|---|---|
null reference |
Results.NoContent() (204). |
paged.IsSuccess == true |
Results.Ok(paged) — serializes the whole envelope (CurrentPage, PageCount, Response, etc.). |
paged.IsSuccess == false |
Casts the value to the non-generic RzR.ResultMessage.Abstractions.IResult (aliased as LibResult) and delegates to ResultToHttpResult.ToHttpResult(...), which produces the RFC 9457 ProblemDetails. |
Why not call ResultToHttpResult.ToHttpResult<T>(paged) directly? Because the upstream generic adapter only serializes result.Response, dropping the paging metadata. ToPagedHttpResult keeps the envelope on success and routes failures through the same ProblemDetails machinery you already use elsewhere.
The optional HttpContext argument is forwarded so the eventual ProblemDetails has access to trace identifiers and instance URLs.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(...);
builder.Services
.AddPagedListResultWeb(o =>
{
o.MaxPageSize = 200;
o.EmitServerTimingHeader = true;
})
.ConfigurePageable<Product>(p =>
{
p.AllowSort("Id", "Name", "Price", "CreatedOn");
p.AllowFilter("Status", "Price", "CreatedOn");
p.AllowSearch("Name", "Description");
});
var app = builder.Build();
var products = app.MapGroup("/products").WithPagedResult();
products.MapGet("/",
async (PagedQueryWithFilters<Product> q, AppDbContext db, HttpContext ctx, CancellationToken ct) =>
{
if (!q.IsValid)
return Results.ValidationProblem(
q.Errors.ToDictionary(e => e.Key, e => new[] { e.Value }));
var paged = await db.Products.AsNoTracking()
.GetPagedWithFiltersAsync(q, cancellationToken: ct);
return paged.ToPagedHttpResult(ctx);
});
app.Run();The endpoint:
- accepts
?page=,?pageSize=,?search=,?order=,?filter=and the rest of the query-string syntax; - validates the request against the per-entity allow-list;
- emits
X-Total-Count,X-Page-Count,X-Page-Size,X-Current-Page, the RFC 5988Linkheader (only because the verb is GET) and the optionalServer-Timingheader; - on success returns
200 OKwith the fullPagedResult<Product>envelope as JSON; - on failure returns
application/problem+jsonwith per-row error keys.
-
Envelope missing on success. If you return
Results.Ok(paged.Response), only theResponsecollection is serialized. UseToPagedHttpResult(orResults.Ok(paged)) to keep the metadata. -
Headers missing. Make sure you registered the endpoint filter with
WithPagedResult()on the route group or endpoint. The MVCPagedResultActionFilterdoes not run for minimal-API endpoints. -
IsValidis always true even with bad input. Verify that the endpoint signature usesPagedQuery<T>/PagedQueryWithFilters<T>(not the rawPagedRequest); only the wrappers exposeIsValid/Errors. -
Type collision on
IResult. ASP.NET Core'sMicrosoft.AspNetCore.Http.IResultcollides withRzR.ResultMessage.Abstractions.IResult. InsidePagedListResult.Webwe use the aliasLibResult = RzR.ResultMessage.Abstractions.IResult. In your own endpoint code prefer the fully-qualified name when you need both, or add a similar alias at the top of the file. -
Server-Timingnot visible.EmitServerTimingHeaderdefaults tofalse. Set it totruein the options to opt in.
PagedListResult is maintained by RzR. GitHub repository.
Start here
Reference
- Data Models Reference
- Expression Builders (Common)
- EntityFramework Core Extensions
- Query String Syntax
ASP.NET Core integration
- Web Integration Overview
- MVC Integration
- Minimal API Integration
- Configuration and Options
- Allow List Registry
- Response Headers and ProblemDetails
- OpenAPI and ApiExplorer
Operations