Repository navigation
Response Headers and ProblemDetails
The Web integration shapes the HTTP response in two complementary ways:
-
Headers — emitted by
PagedResponseHeaderWriter, invoked fromPagedResultActionFilter(MVC) orPagedResultEndpointFilter(minimal API). -
Body — JSON envelope on success (
PagedResult<T>), RFC 9457 ProblemDetails on failure (built by the upstreamIProblemDetailsResultFactory).
| Header | Source | Default | Notes |
|---|---|---|---|
X-Total-Count |
IPagedResult.RowCount |
enabled | Toggle with EmitTotalCountHeader. |
X-Page-Count |
IPagedResult.PageCount |
enabled | Toggle with EmitTotalCountHeader. |
X-Page-Size |
IPagedResult.PageSize |
enabled | Toggle with EmitTotalCountHeader. |
X-Current-Page |
IPagedResult.CurrentPage |
enabled | Toggle with EmitTotalCountHeader. |
Link |
derived | enabled | RFC 5988. Only emitted when the request method is GET and PageCount > 0. The other query parameters are preserved verbatim and only page= is rewritten for first, prev, next, last. |
Server-Timing |
IPagedResult.ExecutionDetails.ExecutionTimeMs |
disabled |
paged;dur=<ms>. Toggle with EmitServerTimingHeader. Skipped when ExecutionTimeMs == -1 (not measured). |
The four X-* headers are toggled together by EmitTotalCountHeader because they constitute a single conceptual unit (the paging cursor).
The Link header is intentionally limited to GET because for POST/PUT the URL is not a fully meaningful representation of the request. If you serve paginated POST endpoints and want a navigable link header, consider exposing a parallel GET endpoint and setting Link from your own code.
Request:
GET /api/products?page=2&pageSize=20&order=price:desc HTTP/1.1
Response:
HTTP/1.1 200 OK
Content-Type: application/json
X-Total-Count: 137
X-Page-Count: 7
X-Page-Size: 20
X-Current-Page: 2
Link: </api/products?pageSize=20&order=price:desc&page=1>; rel="first",
</api/products?pageSize=20&order=price:desc&page=1>; rel="prev",
</api/products?pageSize=20&order=price:desc&page=3>; rel="next",
</api/products?pageSize=20&order=price:desc&page=7>; rel="last"
Server-Timing: paged;dur=42
PagedResponseHeaderWriter.TryApply accepts any object and recognizes a paged result through three mechanisms, in order:
- The object directly implements
IPagedResult(the non-generic abstraction). - The object exposes a
Valueproperty (e.g.ObjectResult,JsonResult,Ok(...)minimal-API result). The writer unwraps the value viaPagedAccessors(cached reflection delegates) and re-checks againstIPagedResult. - Implements
IPagedResult<>for someT(recognized through reflection).
If none of the above matches, the writer is a no-op. This is why your action can return either PagedOkResult(paged) or a custom IActionResult that wraps the same envelope, and the headers will still appear.
The success body is the full PagedResult<T> envelope, serialized as JSON with the framework defaults (or the JsonSerializerOptions you set on PagedListResultWebOptions):
For minimal-API endpoints, you must return paged.ToPagedHttpResult(ctx) (or Results.Ok(paged)) to keep the envelope. See Minimal API Integration.
When the result envelope reports failure (IsSuccess == false) or the request fails validation, the response body is application/problem+json produced by the upstream RzR.ResultMessage.Web.IProblemDetailsResultFactory:
{
"type": "https://datatracker.ietf.org/doc/html/rfc9457",
"title": "One or more validation errors occurred.",
"status": 400,
"detail": "Operator 'Between' requires a CompareValue (upper bound).",
"instance": "/api/products",
"errors": {
"filter[1]": [ "Operator 'Between' requires a CompareValue (upper bound)." ]
},
"messages": [
{ "code": "...", "message": "..." }
]
}The factory honors the following options:
-
PagedListResultWebOptions.ProblemTypeBaseUri— overrides thetypeURI. -
PagedListResultWebOptions.StatusCodeMapper— lets you compute a custom status code from the failingIResult.
You can replace the entire factory by registering your own implementation of IProblemDetailsResultFactory before AddPagedListResultWeb:
services.AddSingleton<IProblemDetailsResultFactory, MyProblemFactory>();
services.AddPagedListResultWeb();The minimal-API helper ToPagedHttpResult returns Results.NoContent() when the paged reference is null. This is intentional: a null paged result means the action decided there is no body to return (e.g. authorization stripped the response). Returning a 204 keeps the contract clean and avoids serializing a null envelope.
The MVC BaseApiPagedResultController does not have a built-in null shortcut; if you want the same behavior, write if (result is null) return NoContent(); at the top of your action.
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
{ "currentPage": 2, "pageCount": 7, "pageSize": 20, "rowCount": 137, "executionDetails": { "executionTimeMs": 42, "executionDate": "2025-01-01T12:00:00.000Z" }, "response": [ { "id": 1, "name": "...", "price": 9.99 }, ... ], "isSuccess": true, "messages": [] }