Skip to content

Web Integration Overview

RzR edited this page Jul 4, 2026 · 2 revisions

Web Integration Overview

PagedListResult.Web (assembly RzR.ResultMessage.Pagination.AspNetCore) connects the EF Core extensions to ASP.NET Core. It is the single package you add when you want a paged endpoint to "just work": query-string parsing, model binding, success and failure shaping, response headers, OpenAPI annotations, and an opt-in allow-list of sortable / filterable / searchable properties.

This page is the high-level map of the integration. Each component links to its own page for the detailed reference.

Service surfaces at a glance

Component Type When it is used
AddPagedListResultWeb(...) DI registration Once in Program.cs.
AddPagedListResultApiExplorer() IMvcBuilder extension Once in Program.cs, registers the OpenAPI convention.
BaseApiPagedResultController Base controller class Inherit from it instead of ControllerBase.
PagedOkResult<T> / PagedXmlResult<T> Action result helpers Return them from controller actions.
[FromPagedQuery] Action-parameter attribute Signal that a PagedRequest should be bound from the query string.
PagedQuery<T> / PagedQueryWithFilters<T> Dual-host query wrappers Accept them directly in a Minimal API endpoint delegate, or as [FromPagedQuery] MVC action parameters (.NET 7+). Namespace RzR.ResultMessage.Pagination.AspNetCore.Query.
WithPagedResult() IEndpointConventionBuilder extension Register the endpoint filter that emits headers (.NET 7+).
ToPagedHttpResult(...) IPagedResult<T> extension Build the minimal-API IResult that preserves the envelope on success and routes failures through ProblemDetails (.NET 7+).
PagedListResultBuilder.ConfigurePageable<T>(...) Fluent registration Restrict which properties may be sorted/filtered/searched per entity type.

Lifecycle of a paged request

HTTP request
   |
   v
Model binder (MVC) or PagedQuery<T>.BindAsync (minimal API)
   |
   |-- parses query string via PagedRequestQueryParser
   |-- consults the allow-list (PageableMetadataRegistry)
   |-- writes ModelState errors per malformed filter row
   v
Controller action / endpoint delegate
   |
   |-- runs your business logic, calls GetPaged* on EF Core
   v
Action filter (MVC) or endpoint filter (minimal API)
   |
   |-- unwraps the action/endpoint result Value
   |-- if the value implements IPagedResult, calls PagedResponseHeaderWriter
   |-- writes X-Total-Count / X-Page-Count / X-Page-Size / X-Current-Page
   |-- writes Link header (RFC 5988) only for GET requests when PageCount > 0
   |-- writes Server-Timing: paged;dur=<ms> if EmitServerTimingHeader is true
   v
Response shaper
   |
   |-- MVC: PagedOkResult / PagedXmlResult
   |-- Minimal API: ToPagedHttpResult
   v
HTTP response
   |
   |-- 200 OK with the full envelope on success
   |-- 204 No Content if the paged result reference is null
   |-- application/problem+json (RFC 9457) on failure

Key components

Configuration

  • AddPagedListResultWeb(Action<PagedListResultWebOptions>?) registers the options, the singleton PageableMetadataRegistry (only if the consumer did not already supply one), and amends MvcOptions to insert the model binder provider at index 0, the application-model convention, and a ServiceFilterAttribute(typeof(PagedResultActionFilter)).
  • AddPagedListResultApiExplorer() is a thin IMvcBuilder extension that adds PagedResultApplicationModelConvention if it is not already present.
  • WithPagedResult() / AddPagedResultEndpointFilter<TBuilder>() (.NET 7+) attach PagedResultEndpointFilter to a route group or endpoint.

See Configuration and Options for the full options reference.

Model binding

  • [FromPagedQuery] opts a PagedRequest-derived parameter into query-string binding.
  • PagedRequestModelBinderProvider returns the binder only when the attribute is present and the model type is assignable from PagedRequest.
  • PagedRequestModelBinder resolves the per-type PageableMetadata allow-list and delegates to PagedRequestQueryParser.Populate.

See MVC Integration and Query String Syntax.

Query wrappers: dual-host binding (.NET 7+)

  • PagedQuery<TEntity> and PagedQueryWithFilters<TEntity> (namespace RzR.ResultMessage.Pagination.AspNetCore.Query, renamed from ...MinimalApi in v5) are dual-host bindable wrappers. In Minimal API they self-bind via BindAsync(HttpContext); in MVC they bind when used as [FromPagedQuery] action parameters. Both implement the shared IPagedQueryValidation abstraction, which exposes IsValid/Errors consistently in either host.
  • Internally, the Minimal-API path uses MinimalApiQueryBinder (an internal implementation detail, the counterpart of the MVC PagedRequestModelBinder) — it is not part of the public surface.

See Minimal API Integration and MVC Integration.

Response shaping

  • MVC: BaseApiPagedResultController exposes PagedOkResult<T>(IPagedResult<T>) (returns 200 OK JSON or application/problem+json) and PagedXmlResult<T>(IPagedResult<T>) (returns SOAP/XML or ProblemDetails). Older JsonResult<T> / XmlResult<T> helpers are kept as [Obsolete] shims for one release.
  • Minimal API: ToPagedHttpResult(this IPagedResult<T>, HttpContext?) builds a Microsoft.AspNetCore.Http.IResult that preserves the full envelope on success.
  • PagedResultActionFilter (MVC) and PagedResultEndpointFilter (minimal API) write the response headers without changing the body.

See Response Headers and ProblemDetails.

Discoverability

  • PagedResultApplicationModelConvention walks every controller action that returns an IPagedResult<T> (or wraps one in Task<>, ValueTask<>, ActionResult<>) and attaches Produces("application/json"), ProducesResponseType(typeof(PagedResult<T>), 200) and ProducesResponseType(typeof(ProblemDetails), 400) if the user has not supplied them.
  • PageableMetadataRegistry (default IPageableMetadataRegistry implementation) maintains a ConcurrentDictionary<Type, PageableMetadata> of allow-listed properties per entity type. Populated through PagedListResultBuilder.ConfigurePageable<T>(...) during registration.

See OpenAPI and ApiExplorer and Allow List Registry.

What you typically write yourself

Most consumers only need three things:

// Program.cs
builder.Services
    .AddControllers()
    .AddPagedListResultApiExplorer();

builder.Services
    .AddPagedListResultWeb(o =>
    {
        o.MaxPageSize = 100;
    })
    .ConfigurePageable<Product>(p =>
    {
        p.AllowSort("Id", "Name", "Price", "CreatedOn");
        p.AllowFilter("Status", "Price", "CreatedOn");
        p.AllowSearch("Name", "Description");
    });
// Controller
public sealed class ProductsController : BaseApiPagedResultController
{
    [HttpPost]
    public async Task<IActionResult> List(
        [FromBody] PageRequestWithFilters request, CancellationToken ct) =>
        PagedOkResult(await _db.Products.AsNoTracking()
            .GetPagedWithFiltersAsync(request, cancellationToken: ct));

    [HttpGet]
    public async Task<IActionResult> List(
        [FromPagedQuery(typeof(Product))] PageRequestWithFilters request, CancellationToken ct) =>
        PagedOkResult(await _db.Products.AsNoTracking()
            .GetPagedWithFiltersAsync(request, cancellationToken: ct));
}

That is enough to get the JSON envelope, the ProblemDetails error path, the standard headers and the OpenAPI annotations. The rest of the pages in this section explain how to customize each piece.

Clone this wiki locally