Skip to content

Opinionated paging packages: Postie.Paging and Postie.AspNetCore.Paging #10

Description

@AndrewMcLachlan

What problem would this solve?

Paged list endpoints are one of the most common shapes in a CQRS API, and today every consumer hand-rolls the same three pieces: a result envelope, an endpoint that unwraps it, and a total-count convention for clients. Postie core deliberately ships no envelope opinion — but that leaves real apps (validated while migrating a production app to Postie 1.0) re-implementing an identical pattern per codebase.

Proposed API or behavior

// Postie.Paging — transport- and mediator-neutral, no ASP.NET reference
public record PagedResult<T>
{
    public required IEnumerable<T> Items { get; init; }
    public required int TotalCount { get; init; }
}

// handler side
public record GetTransactions(Guid AccountId, int PageSize, int PageNumber) : IQuery<PagedResult<Transaction>>;

// Postie.AspNetCore.Paging — endpoint side, full MapQuery parity
app.MapPagedQuery<GetTransactions, Transaction>("/accounts/{accountId}/transactions/{pageSize}/{pageNumber}");
app.MapPagedQuery<SearchTransactions, Transaction>("/transactions/search", QueryMethod.Post);
// -> 200 with the unwrapped Items array, X-Total-Count response header,
//    Produces<IEnumerable<Transaction>>, null envelope -> 404;
//    QueryMethod/RequestBinding semantics identical to MapQuery

Two packages so the envelope is usable from handler/application projects with zero ASP.NET framework reference, and works identically for the in-box mediator or MediatR (dispatch goes through IEndpointDispatcher).

Alternatives considered

  • Chaining on the returned RouteHandlerBuilder: can't express this — the unwrap-and-header behaviour lives inside the handler delegate, not in endpoint metadata.
  • Keeping it per-app: works (that's the status quo) but re-implements Postie internals (per-binding delegates, verb mapping, guards) that aren't public — proven while building exactly this as an app-side extension.
  • Putting the envelope in Postie.Cqrs: rejected — core stays unopinionated; paging ships separately precisely because it is an opinion, following the FluentValidation packages' precedent.

Contribution

  • I'm willing to send a PR for this

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions