Overview
PagedListResult is a focused .NET library that standardizes server‑side pagination, sorting, searching, and filtering into a clear request → execution → result pipeline. The repository is intentionally opinionated: instead of exposing many small helpers, it defines strong contracts (request and result models) and a set of query extensions that apply those contracts to IQueryable<T> sources.
The primary goal of the project is to eliminate repetitive pagination logic in APIs and application services, while keeping the query behavior explicit, testable, and extensible.
Design Philosophy
The repository follows several consistent design principles that are visible across all projects:
- Contract‑first pagination – Pagination is driven by a request model rather than ad‑hoc parameters.
-
Queryable‑centric execution – All operations are applied to
IQueryable<T>, allowing deferred execution and provider‑level optimizations. - Separation of concerns – Request/response models, query logic, and common helpers are separated into distinct projects.
- API‑oriented output – The result model includes metadata required by front‑end grids and consumers, not just the data subset.
Repository Structure
The solution is split into multiple projects, each with a strictly scoped responsibility. This separation is not cosmetic; it directly reflects how pagination concerns are decomposed and enforced across the library.
1. PagedListResult.DataModels (Core Contract Layer)
This project is the foundation of the entire solution. Every other project depends on it, and it intentionally contains no query execution logic. Its sole purpose is to define stable, serializable contracts that describe what the consumer wants and what the system returns.
Architectural Role
- Acts as a boundary layer between clients (API/UI) and query execution
- Ensures pagination behavior is explicit and versionable
- Makes pagination requests portable across services
This design allows the same request model to be reused in:
- ASP.NET Core controllers
- Application services
- Background jobs
- Integration tests
Key Types and Responsibilities
PagedRequest
The PagedRequest type is the central aggregation root of pagination input. Rather than scattering parameters, it consolidates all query intent into a single object.
Core responsibilities:
- Page index normalization
- Page size definition
- Optional ordering definition
- Optional search definition
- Optional filter collection
The model is intentionally permissive: most properties are optional, allowing minimal or advanced usage with the same contract.
Paging Semantics
Paging is expressed in logical terms, not execution terms:
-
Pagerepresents the requested page index -
PageSizerepresents the logical size of the page
No assumptions are made about storage or transport. Execution layers decide how to translate these into Skip / Take operations.
OrderOptions
OrderOptions defines ordering intent without binding to LINQ or database specifics.
Key characteristics:
- Property name is expressed as a string
- Direction is represented as a semantic enum or value
- Designed to be validated before execution
This allows clients to control ordering while keeping execution safe and predictable.
SearchOptions
Search is modeled explicitly rather than as a generic filter.
Responsibilities:
- Store the search term
- Define whether the search applies to all text fields
- Enable consistent interpretation of search intent
By isolating search into its own model, the library avoids overloading filters with responsibilities they are not suited for.
Filter Models
Filtering is expressed through a structured hierarchy rather than ad-hoc predicates.
Typical filter elements include:
- Target property name
- Condition operator (equality, range, containment, etc.)
- One or more comparison values
-
ANDorORcomparation apply between values/filters - Dependence between filters
This structure enables:
- Validation before execution
- Deterministic expression generation
- Consistent behavior across endpoints
PagedResult
PagedResult<T> is the authoritative output contract of the library.
It contains:
- The paged response collection
- Paging metadata (page, page size, page count)
- Total row count
- Execution diagnostics
Importantly, the result is self-describing: consumers do not need to recompute or infer paging state.
ExecutionDetails
Execution diagnostics are modeled explicitly rather than logged implicitly.
This enables:
- Performance analysis at the API boundary
- Transparent reporting to consumers
- Easier debugging in distributed systems
2. PagedListResult.Common (Query Interpretation Layer)
This project translates intent into executable logic.
Its responsibility is to convert DataModel contracts into expression trees that can be applied to IQueryable<T> without knowing the underlying provider.
Core Responsibilities
- Validate property access at runtime
-
Build expression trees for:
- Filters
- Search
- Ordering
Combine multiple expressions deterministically
Expression-Driven Design
Rather than building queries imperatively, the library constructs expression trees. This allows:
- Provider-level optimization
- Deferred execution
- Compatibility with LINQ providers
Each query concern (filter, search, order) is handled independently and then composed.
3. PagedListResult (Execution & Integration Layer)
This project is the behavioral core of the solution. While DataModels defines what should happen and Common defines how intent is translated, PagedListResult defines when and in what order everything is executed.
It is also the only project that consumers typically reference directly.
Public API Surface
The project exposes a minimal but powerful public surface:
- Extension methods over
IQueryable<T> - Strongly typed pagination execution returning
PagedResult<T>
This design deliberately avoids:
- Base repository abstractions
- Framework‑specific dependencies at the API boundary
- Hidden execution side effects
Consumers remain in full control of when queries are materialized.
Extension Method Architecture
Pagination is implemented entirely via extension methods rather than services or static helpers.
Key implications:
- Pagination composes naturally with LINQ
- No dependency injection is required
- Domain entities remain untouched
A typical execution entry point:
query.GetPagedAsync(pagedRequest);
query.GetPagedWithFiltersAsync(pagedRequest);
query.GetPagedWithMainFiltersAsync(pagedRequest);
Internally, this single call coordinates the entire pipeline.
Execution Orchestration Responsibilities
The PagedListResult project does not build expressions itself. Instead, it orchestrates execution by delegating responsibilities in a strict order.
Its responsibilities include:
- Guard‑clause validation of the request
- Delegation of search, filter, and order logic
- Pagination window calculation
- Execution diagnostics capture
- Final result assembly
This ensures that query semantics remain centralized and predictable.
Ordering Strategy Enforcement
One subtle but critical responsibility of this layer is ordering enforcement.
Stable pagination requires deterministic ordering. The execution layer ensures that:
- Explicit ordering is applied when provided
- A fallback strategy exists when ordering is missing
- Ordering is applied before pagination
Without this enforcement, page boundaries could shift between executions.
Row Count Evaluation
Row count is evaluated before pagination is applied.
This design decision ensures:
- Accurate
RowCount - Correct
PageCount - Independence from page size or index
The execution layer explicitly separates:
- Total dataset size
- Paged window size
This distinction is critical for client‑side pagination controls.
Pagination Window Calculation
The execution layer translates logical paging intent into execution behavior:
- Calculates skip offset from page index and page size
- Applies
SkipandTakeonly after all query conditions
This guarantees that pagination is applied to the final filtered and ordered dataset.
ExecutionDetails Population
Execution diagnostics are captured as part of the core workflow.
Typical captured details include:
- Start and end timestamps
- Total execution duration
By embedding diagnostics in the result rather than logging implicitly, the library enables:
- Transparent performance reporting
- API‑level observability
- Easier troubleshooting without log access
Result Assembly
The final step is deterministic result construction.
The execution layer assembles:
- Materialized response data
- Paging metadata
- Execution diagnostics
The resulting PagedResult<T> is immediately consumable by API clients without additional transformation.
Detailed Pagination Execution Pipeline
The execution pipeline is deterministic and ordered to avoid ambiguity.
- Request Validation
- Page boundaries
- Page size constraints
- Property existence for filters and ordering
- Search Application
- Text-based expressions generated first
- Applied before filters to reduce dataset early
- Filter Application
- Structured conditions translated into expressions
- Combined using logical
ANDorORsemantics
- Ordering Application
- Explicit ordering or fallback strategy
- Ensures stable pagination
- Row Count Evaluation
- Executed before paging
- Represents total matching dataset size
- Pagination Application
-
SkipandTakeapplied last
- Result Materialization
- Execution timing captured
-
PagedResult<T>populated
Structured Result Serialization: JSON and XML Support
One important capability of the PagedListResult project that deserves explicit attention is its built‑in support for result serialization targeting JSON and XML consumers.
This functionality is not an afterthought; it is reflected directly in the source code and aligns with the library’s API‑first design.
Why Serialization Is a First‑Class Concern
The library assumes that paged results are most often:
- Returned from REST APIs
- Consumed by heterogeneous clients
- Persisted or logged in structured formats
For that reason, the result model (PagedResult<T>) and its related metadata types are designed to be:
- Fully serializable
- Deterministic in structure
- Independent of execution context
This makes the result suitable for both JSON‑based APIs and XML‑based integrations without custom mapping layers.
JSON Result Shape
From the source perspective, the JSON representation is a direct projection of the result contract:
- Paging metadata is exposed as top‑level fields
- Execution details are explicitly represented
- The response collection is serialized as a structured array
Because the library avoids custom converters or runtime mutation, the JSON output remains:
- Stable across executions
- Predictable for front‑end grids
- Easy to version
This is particularly important for client‑side pagination components that depend on fixed property names.
XML Result Support
The repository also supports XML‑friendly result structures.
Key characteristics visible in the source:
- Result and metadata models are designed with XML serialization compatibility in mind
- No reliance on JSON‑only attributes or behaviors
- Clean hierarchical structure suitable for XML consumers
This enables scenarios such as:
- Legacy system integrations
- SOAP‑adjacent services
- Inter‑service communication where XML is still required
The same PagedResult<T> contract can be serialized to XML without transformation, preserving paging semantics and metadata.
Search Behavior and Serialization Alignment
Search functionality (including text search across multiple fields) is intentionally decoupled from serialization concerns.
However, the results of search operations are fully reflected in the serialized output:
-
RowCountrepresents the count after search and filters -
PageCountreflects the searched dataset - The response collection contains only matching items
This guarantees that JSON or XML consumers always receive a consistent and truthful representation of the executed query.
Design Implications
By designing serialization‑friendly result contracts, the library achieves:
- Zero transformation overhead at API boundaries
- Reduced controller/service boilerplate
- Clear separation between execution logic and representation
This reinforces the repository’s broader architectural theme: explicit contracts over implicit behavior.
Summary
PagedListResult is not only a pagination and query‑execution library; it is also a transport‑ready result framework.
Its built‑in alignment with JSON and XML serialization ensures that paged results can move safely and consistently across system boundaries, making it especially suitable for real‑world API and integration scenarios.
For teams working with mixed client ecosystems or strict API contracts, this aspect of the library is a critical—and often underappreciated—strength.
PagedListResult is not a simple paging helper; it is a contract-driven pagination framework designed for service-oriented .NET applications.
By separating:
- intent (DataModels)
- interpretation (Common)
- execution (Core)
…the repository achieves a high level of clarity, safety, and extensibility.
For teams building APIs that require consistent, transparent, and extensible pagination behavior, this project provides a strong and thoughtfully designed foundation.
PagedListResult is a strongly structured pagination solution for .NET developers who want:
- Explicit pagination contracts
- Centralized query logic
- Rich response metadata
- Minimal repetition across endpoints
The repository demonstrates a clean separation between data contracts, query infrastructure, and execution logic, making it both approachable for consumers and extensible for advanced scenarios.
For developers building serious, data‑driven APIs, this repository provides a solid and opinionated foundation for pagination done right.
Repository: https://github.com/I-RzR-I/PagedListResult
Top comments (0)