From 2625003094fd58be0aadb59e391ceb94b1688fcb Mon Sep 17 00:00:00 2001 From: DrSkillIssue Date: Fri, 21 Aug 2026 17:11:09 -0500 Subject: [PATCH] feat: reject page sizes above the maximum The request-driven PaginateAsync overloads throw BadHttpRequestException when request.PageSize exceeds maxPageSize, replacing the silent clamp. The fluent builder's MaxPageSize and ExecutionOptions keep their clamp. Bump the version to 3.2.0. --- Directory.Build.props | 2 +- docs/api-reference.md | 2 +- .../PaginationEndpointExtensions.cs | 12 +++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f321b18..8a9f2ac 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,7 +22,7 @@ - 3.1.0 + 3.2.0 DrSkillIssue MIT https://github.com/DrSkillIssue/EFPagination diff --git a/docs/api-reference.md b/docs/api-reference.md index 711b736..c35d694 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -619,7 +619,7 @@ Task> PaginateAsync( The `Expression>` overloads run the projection inside the SQL statement that applies the keyset `ORDER BY`. Subqueries inside the selector stay server-side and the SELECT list materializes only the projected columns plus the keyset key columns. The pagination definition must have 1–8 key columns; outside that range, the overload throws `NotSupportedException`. -Every `PaginateAsync` overload throws `BadHttpRequestException` (status 400) for an invalid or expired cursor and for a `request.PageSize` of zero or less. ASP.NET Core's exception handler middleware maps its status code to the response. +Every `PaginateAsync` overload throws `BadHttpRequestException` (status 400) for an invalid or expired cursor, for a `request.PageSize` of zero or less, and for a `request.PageSize` above `maxPageSize`. ASP.NET Core's exception handler middleware maps its status code to the response. See [Server-Side Projection](patterns.md#server-side-projection) for end-to-end examples. diff --git a/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs b/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs index ec53042..5147809 100644 --- a/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs +++ b/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs @@ -23,6 +23,7 @@ public static class PaginationEndpointExtensions /// Whether to compute the total row count. /// A cancellation token. /// A task that resolves to a . + /// The request's cursor is invalid or expired, or its page size is not a positive integer of at most . public static async Task> PaginateAsync( this IQueryable query, PaginationQueryDefinition definition, @@ -32,6 +33,9 @@ public static async Task> PaginateAsync( bool includeCount = false, CancellationToken ct = default) where T : class { + if (request.PageSize > maxPageSize) + throw new BadHttpRequestException($"pageSize '{request.PageSize}' exceeds the maximum page size {maxPageSize}."); + var (cursor, direction) = request.Before is not null ? (request.Before, PaginationDirection.Backward) : (request.After, PaginationDirection.Forward); @@ -104,7 +108,7 @@ public static Task> PaginateAsync( /// A cancellation token. /// A task that resolves to a with projected items. /// , , or is . - /// The cursor in is invalid or expired. + /// The request's cursor is invalid or expired, or its page size is not a positive integer of at most . /// The pagination definition has fewer than 1 or more than 8 key columns. public static async Task> PaginateAsync( this IQueryable query, @@ -119,14 +123,16 @@ public static async Task> PaginateAsync( ArgumentNullException.ThrowIfNull(definition); ArgumentNullException.ThrowIfNull(selector); - var effectivePageSize = request.PageSize > maxPageSize ? maxPageSize : request.PageSize; + if (request.PageSize > maxPageSize) + throw new BadHttpRequestException($"pageSize '{request.PageSize}' exceeds the maximum page size {maxPageSize}."); + var builder = query.Keyset(definition).FromRequest(request).MaxPageSize(maxPageSize); if (includeCount) builder = builder.IncludeCount(); try { - var page = await builder.TakeAsync(effectivePageSize, selector, ct).ConfigureAwait(false); + var page = await builder.TakeAsync(request.PageSize, selector, ct).ConfigureAwait(false); return page.ToPaginatedResponse(); }