Skip to main content

Overview

The Intent.Application.Dtos.Pagination module provides pagination infrastructure for your application services. It generates PagedResult<T> and CursorPagedResult<T> types along with mapping extensions for seamless integration with repositories and domain queries.
Module: Intent.Application.Dtos.PaginationVersion: 4.1.5+Dependencies:
  • Intent.Application.Dtos
  • Intent.Modelers.Services
  • Intent.Modelers.Domain

Key Features

  • Offset Pagination: Traditional page number/page size pagination
  • Cursor Pagination: Efficient cursor-based pagination for large datasets
  • Mapping Extensions: Convert repository results to paginated DTOs
  • Configurable Defaults: Set default page sizes and ordering
  • Type Safety: Generic result types for any DTO
  • Integration: Works with EF Core, CosmosDB, MongoDB, Redis OM, and more

Installation

intent install Intent.Application.Dtos.Pagination

Pagination Types

PagedResult<T>

Traditional offset-based pagination using page numbers:
PagedResult.cs
public class PagedResult<T>
{
    public int TotalCount { get; set; }
    public int PageCount { get; set; }
    public int PageSize { get; set; }
    public int PageNumber { get; set; }
    public IEnumerable<T> Data { get; set; }

    public static PagedResult<T> Create(
        int totalCount,
        int pageCount,
        int pageSize,
        int pageNumber,
        IEnumerable<T> data)
    {
        return new PagedResult<T>
        {
            TotalCount = totalCount,
            PageCount = pageCount,
            PageSize = pageSize,
            PageNumber = pageNumber,
            Data = data,
        };
    }
}
Use Cases:
  • UI pagination with page numbers
  • Small to medium datasets
  • When users need to jump to specific pages
  • RESTful APIs with page-based navigation

CursorPagedResult<T>

Cursor-based pagination for efficient traversal:
CursorPagedResult.cs
public class CursorPagedResult<T>
{
    public int PageSize { get; set; }
    public string? PreviousPageCursor { get; set; }
    public string? NextPageCursor { get; set; }
    public IEnumerable<T> Data { get; set; }

    public static CursorPagedResult<T> Create(
        int pageSize,
        string? previousPageCursor,
        string? nextPageCursor,
        IEnumerable<T> data)
    {
        return new CursorPagedResult<T>
        {
            PageSize = pageSize,
            PreviousPageCursor = previousPageCursor,
            NextPageCursor = nextPageCursor,
            Data = data,
        };
    }
}
Use Cases:
  • Infinite scrolling UIs
  • Large datasets
  • Real-time data feeds
  • GraphQL APIs
  • When page jumping is not required

Configuration

Module Settings

Access pagination settings in application settings:
Application Settings > Pagination Settings

Page Size Default

Type: number Default: 20 Sets the default page size for all paginated services.

Order By Default

Type: text Default: "" (empty) Defines the default ordering expression for paginated queries (e.g., "Name", "CreatedDate desc").

Usage in Services

Defining Paginated Operations

In the Services Designer, you can mark operations to return paginated results:
1

Create Service Operation

Add an operation to your service (e.g., GetProducts)
2

Set Return Type

Set the return type to your DTO (e.g., ProductDto)
3

Apply Paging Stereotype

Apply the Paginated stereotype to the operation
4

Generate Code

Run the Software Factory - return type becomes PagedResult<ProductDto>

Generated Service Contract

IProductService.cs
public interface IProductService
{
    Task<PagedResult<ProductDto>> GetProductsAsync(
        int pageNo,
        int pageSize,
        CancellationToken cancellationToken = default);
}

Implementation Example

ProductService.cs
public class ProductService : IProductService
{
    private readonly IProductRepository _repository;
    private readonly IMapper _mapper;

    public ProductService(IProductRepository repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }

    public async Task<PagedResult<ProductDto>> GetProductsAsync(
        int pageNo,
        int pageSize,
        CancellationToken cancellationToken)
    {
        // Get paginated data from repository
        var products = await _repository
            .FindAllAsync(pageNo, pageSize, cancellationToken);

        // Map to DTOs using generated extension
        return products.MapToPagedResult(_mapper.ProjectTo<ProductDto>);
    }
}

Mapping Extensions

The module generates extension methods for converting between paginated types:

PagedResult Mapping Extensions

Generated Extensions
public static class PagedResultMappingExtensions
{
    public static PagedResult<TDto> MapToPagedResult<TSource, TDto>(
        this IPagedResult<TSource> pagedResult,
        Func<IQueryable<TSource>, IQueryable<TDto>> projection)
    {
        return PagedResult<TDto>.Create(
            totalCount: pagedResult.TotalCount,
            pageCount: pagedResult.PageCount,
            pageSize: pagedResult.PageSize,
            pageNumber: pagedResult.PageNo,
            data: projection(pagedResult.AsQueryable()).ToList());
    }

    public static PagedResult<TDto> MapToPagedResult<TSource, TDto>(
        this IPagedResult<TSource> pagedResult,
        Func<TSource, TDto> mapping)
    {
        return PagedResult<TDto>.Create(
            totalCount: pagedResult.TotalCount,
            pageCount: pagedResult.PageCount,
            pageSize: pagedResult.PageSize,
            pageNumber: pagedResult.PageNo,
            data: pagedResult.Select(mapping).ToList());
    }
}

CursorPagedResult Mapping Extensions

Cursor Extensions
public static class CursorPagedResultMappingExtensions
{
    public static CursorPagedResult<TDto> MapToCursorPagedResult<TSource, TDto>(
        this ICursorPagedResult<TSource> pagedResult,
        Func<IQueryable<TSource>, IQueryable<TDto>> projection)
    {
        return CursorPagedResult<TDto>.Create(
            pageSize: pagedResult.PageSize,
            previousPageCursor: pagedResult.PreviousPageCursor,
            nextPageCursor: pagedResult.NextPageCursor,
            data: projection(pagedResult.AsQueryable()).ToList());
    }
}

Advanced Usage

Filtering and Sorting

Combine pagination with filtering and sorting:
Advanced Query
public async Task<PagedResult<ProductDto>> SearchProductsAsync(
    string? searchTerm,
    string? sortBy,
    bool descending,
    int pageNo,
    int pageSize,
    CancellationToken cancellationToken)
{
    var query = _repository.Query();

    // Apply filtering
    if (!string.IsNullOrEmpty(searchTerm))
    {
        query = query.Where(p => p.Name.Contains(searchTerm));
    }

    // Apply sorting
    query = sortBy?.ToLower() switch
    {
        "name" => descending 
            ? query.OrderByDescending(p => p.Name)
            : query.OrderBy(p => p.Name),
        "price" => descending
            ? query.OrderByDescending(p => p.Price)
            : query.OrderBy(p => p.Price),
        _ => query.OrderBy(p => p.CreatedDate)
    };

    // Execute paginated query
    var pagedResult = await _repository
        .FindAllAsync(query, pageNo, pageSize, cancellationToken);

    return pagedResult.MapToPagedResult(_mapper.ProjectTo<ProductDto>);
}

Cursor-Based Pagination

For cursor-based pagination with repositories:
Cursor Pagination
public async Task<CursorPagedResult<ProductDto>> GetProductsCursorAsync(
    string? cursor,
    int pageSize,
    CancellationToken cancellationToken)
{
    var pagedResult = await _repository
        .FindAllAsync(cursor, pageSize, cancellationToken);

    return pagedResult.MapToCursorPagedResult(_mapper.ProjectTo<ProductDto>);
}

Empty Results

Handle empty result sets gracefully:
Empty Result
public async Task<PagedResult<ProductDto>> GetProductsAsync(
    int pageNo,
    int pageSize,
    CancellationToken cancellationToken)
{
    var products = await _repository.FindAllAsync(pageNo, pageSize, cancellationToken);

    if (products.TotalCount == 0)
    {
        return PagedResult<ProductDto>.Create(
            totalCount: 0,
            pageCount: 0,
            pageSize: pageSize,
            pageNumber: pageNo,
            data: Enumerable.Empty<ProductDto>());
    }

    return products.MapToPagedResult(_mapper.ProjectTo<ProductDto>);
}

Integration with Repositories

The pagination module automatically integrates with repository interfaces:

Entity Framework Core

EF Core Repository
public interface IProductRepository : IEFRepository<Product, IApplicationDbContext>
{
    Task<IPagedResult<Product>> FindAllAsync(
        int pageNo,
        int pageSize,
        CancellationToken cancellationToken = default);
}

CosmosDB

CosmosDB Repository
public interface IProductRepository : ICosmosDBRepository<Product>
{
    Task<ICursorPagedResult<Product>> FindAllAsync(
        string? cursor,
        int pageSize,
        CancellationToken cancellationToken = default);
}

MongoDB

MongoDB Repository
public interface IProductRepository : IMongoRepository<Product>
{
    Task<IPagedResult<Product>> FindAllAsync(
        int pageNo,
        int pageSize,
        CancellationToken cancellationToken = default);
}

Best Practices

  • Use offset pagination (PagedResult) for UI grids, admin panels, and reports
  • Use cursor pagination (CursorPagedResult) for feeds, activity streams, and mobile apps
  • Consider your data size: cursor pagination scales better for millions of records
  • Default to 20-50 items per page for most use cases
  • Limit maximum page size to prevent performance issues (e.g., 100)
  • Adjust based on payload size (smaller items = larger page size)
Always project to DTOs at the database level using AutoMapper’s ProjectTo:
// ✅ Good - Single query with projection
var result = await _repository
    .FindAllAsync(pageNo, pageSize, ct);
return result.MapToPagedResult(_mapper.ProjectTo<ProductDto>);

// ❌ Bad - Multiple queries and over-fetching
var result = await _repository.FindAllAsync(pageNo, pageSize, ct);
return result.MapToPagedResult(p => _mapper.Map<ProductDto>(p));
Always return TotalCount and PageCount so clients can:
  • Display “Showing 1-20 of 157 results”
  • Render page numbers accurately
  • Disable next/previous buttons appropriately

Frontend Integration

REST API Response

Example Response
{
  "totalCount": 157,
  "pageCount": 8,
  "pageSize": 20,
  "pageNumber": 1,
  "data": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "Premium Widget",
      "price": 29.99
    },
    // ... 19 more items
  ]
}

React Example

React Component
const ProductList = () => {
  const [page, setPage] = useState(1);
  const [data, setData] = useState<PagedResult<ProductDto>>();

  useEffect(() => {
    fetch(`/api/products?pageNo=${page}&pageSize=20`)
      .then(res => res.json())
      .then(setData);
  }, [page]);

  return (
    <div>
      <ProductGrid products={data?.data ?? []} />
      <Pagination
        current={data?.pageNumber}
        total={data?.totalCount}
        pageSize={data?.pageSize}
        onChange={setPage}
      />
    </div>
  );
};

Troubleshooting

Issue: Service operation doesn’t return paginated results.Solution:
  1. Verify the Paginated stereotype is applied in the designer
  2. Run the Software Factory to regenerate contracts
  3. Check that the repository implements the pagination interface
Issue: MapToPagedResult method not available.Solution:
  1. Ensure Intent.Application.Dtos.Pagination is installed
  2. Check the namespace imports include YourApp.Application.Common.Pagination
  3. Rebuild the solution
Issue: Paginated queries are slow.Solution:
  1. Add database indexes on frequently sorted/filtered columns
  2. Use ProjectTo for AutoMapper projections
  3. Limit maximum page size
  4. Consider cursor pagination for very large datasets

External Resources

Build docs developers (and LLMs) love