Miccore.Net.Pagination 2.0.0

dotnet add package Miccore.Net.Pagination --version 2.0.0
                    
NuGet\Install-Package Miccore.Net.Pagination -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Miccore.Net.Pagination" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Miccore.Net.Pagination" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Miccore.Net.Pagination" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Miccore.Net.Pagination --version 2.0.0
                    
#r "nuget: Miccore.Net.Pagination, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Miccore.Net.Pagination@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Miccore.Net.Pagination&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Miccore.Net.Pagination&version=2.0.0
                    
Install as a Cake Tool

Miccore .Net Pagination

NuGet License: MIT

Server-side pagination library for .NET with Entity Framework Core support.

Requires .NET 8.0 or .NET 10.0

Features

  • ✅ Async and sync pagination methods
  • ✅ Dynamic sorting by property name
  • ✅ IAsyncEnumerable streaming support
  • ✅ Automatic navigation links (prev/next)
  • ✅ Full nullable reference types support
  • ✅ Multi-targeting .NET 8.0 and .NET 10.0

Installation

.NET CLI

dotnet add package Miccore.Net.Pagination

Package Manager

Install-Package Miccore.Net.Pagination

⚠️ Migration Guide v1 → v2

Version 2.0 introduces breaking changes. Follow this guide to migrate from v1.x:

Breaking Changes

v1.x v2.x Action Required
Miccore.Pagination.Model Miccore.Pagination Update namespace imports
Miccore.Pagination.Service Miccore.Pagination Update namespace imports
RouterLink class RouteLink class Rename class usage
query.paginate query.Paginate Update property names (PascalCase)
query.page query.Page Update property names (PascalCase)
query.limit query.Limit Update property names (PascalCase)
.NET 6.0 .NET 8.0 / 10.0 Upgrade target framework

Migration Steps

  1. Update your target framework in your .csproj:
<TargetFramework>net8.0</TargetFramework>
  1. Update namespace imports:
// Before (v1)
using Miccore.Pagination.Models;
using Miccore.Pagination.Service;

// After (v2)
using Miccore.Pagination;
  1. Update PaginationQuery properties to PascalCase:
// Before (v1)
var query = new PaginationQuery { paginate = true, page = 1, limit = 10 };

// After (v2)
var query = new PaginationQuery { Paginate = true, Page = 1, Limit = 10 };
  1. Rename RouterLink to RouteLink (if used directly):
// Before (v1)
RouterLink.AddRouteLink(...)

// After (v2)
RouteLink.AddRouteLink(...)
// Or use as extension method:
response.AddRouteLink(url, query);

🔄 Deterministic Sorting

Important: When using pagination with sorting, ensure your sort order is deterministic to avoid duplicated or missing items across pages.

The Problem

If you sort by a non-unique field (e.g., CreatedAt), multiple items may have the same value. The database may return these items in an inconsistent order, causing:

  • Items appearing on multiple pages
  • Items being skipped entirely

The Solution

Always add a secondary sort on a unique field (like Id):

// ❌ Non-deterministic - may cause issues
var result = await _context.Products
    .ApplySort("CreatedAt", "desc")
    .PaginateAsync(query);

// ✅ Deterministic - safe for pagination
var result = await _context.Products
    .ApplySort("CreatedAt", "desc")
    .ThenBy(x => x.Id)  // Secondary sort ensures uniqueness
    .PaginateAsync(query);

Quick Start

Basic Usage

using Miccore.Pagination;
using Microsoft.Extensions.DependencyInjection;

// In your repository or service
public async Task<PaginationModel<Product>> GetProductsAsync(PaginationQuery query)
{
    return await _context.Products.PaginateAsync(query);
}

// In your controller
[HttpGet]
public async Task<ActionResult<PaginationModel<ProductDto>>> GetProducts([FromQuery] PaginationQuery query)
{
    var products = await _productService.GetProductsAsync(query);
    var response = _mapper.Map<PaginationModel<ProductDto>>(products);
    
    if (query.Paginate)
    {
        response.AddRouteLink(Url.RouteUrl(nameof(GetProducts)), query);
    }
    
    return Ok(response);
}

With Sorting

// Request: GET /api/products?paginate=true&page=1&limit=10&orderBy=Price&orderDirection=desc
[HttpGet]
public async Task<ActionResult<PaginationModel<ProductDto>>> GetProducts([FromQuery] PaginationQuery query)
{
    // Sorting is automatically applied based on query parameters
    var products = await _context.Products
        .PaginateAsync(query);
    
    // Response includes sorting info
    // {
    //   "currentPage": 1,
    //   "totalPages": 5,
    //   "sortedBy": "Price",
    //   "sortDirection": "desc",
    //   "items": [...]
    // }
    
    return Ok(products);
}

Synchronous Pagination

// For scenarios where async is not suitable
var result = _context.Products.Paginate(query);

Streaming with IAsyncEnumerable

// Stream all items (no pagination)
await foreach (var product in _context.Products.AsStreamAsync("Name", "asc"))
{
    await ProcessProduct(product);
}

// Stream paginated items
await foreach (var product in _context.Products.PaginateAsStreamAsync(query))
{
    await ProcessProduct(product);
}

API Reference

PaginationQuery

Property Type Default Description
Paginate bool false Enable/disable pagination
Page int 1 Page number (1-indexed)
Limit int 10 Items per page
OrderBy string? null Property name to sort by
OrderDirection string "asc" Sort direction ("asc" or "desc")

PaginationModel<T>

Property Type Description
PageSize int Items per page (max 100)
CurrentPage int Current page number
TotalItems int Total item count
TotalPages int Total page count
Items List<T> Items for current page
Prev string? Previous page URL
Next string? Next page URL
SortedBy string? Applied sort property
SortDirection string? Applied sort direction

Extension Methods

Method Description
PaginateAsync<T>() Async pagination with sorting
Paginate<T>() Sync pagination with sorting
ApplySort<T>() Apply dynamic sorting
AsStreamAsync<T>() Stream all items
PaginateAsStreamAsync<T>() Stream paginated items
AddRouteLink<T>() Add navigation URLs

Complete Example

Repository

using Miccore.Pagination;
using Microsoft.Extensions.DependencyInjection;

public class NotificationRepository : INotificationRepository
{
    private readonly AppDbContext _context;

    public async Task<PaginationModel<Notification>> GetAllAsync(
        PaginationQuery query,
        CancellationToken cancellationToken = default)
    {
        return await _context.Notifications
            .Where(n => !n.IsDeleted)
            .PaginateAsync(query, cancellationToken);
    }
}

Service

using Miccore.Pagination;

public class NotificationService : INotificationService
{
    private readonly INotificationRepository _repository;
    private readonly IMapper _mapper;

    public async Task<PaginationModel<NotificationDto>> GetAllAsync(
        PaginationQuery query,
        CancellationToken cancellationToken = default)
    {
        var notifications = await _repository.GetAllAsync(query, cancellationToken);
        return _mapper.Map<PaginationModel<NotificationDto>>(notifications);
    }
}

Controller

using Miccore.Pagination;

[ApiController]
[Route("api/[controller]")]
public class NotificationsController : ControllerBase
{
    private readonly INotificationService _service;
    private readonly IMapper _mapper;

    [HttpGet(Name = nameof(GetNotifications))]
    public async Task<ActionResult<PaginationModel<NotificationViewModel>>> GetNotifications(
        [FromQuery] PaginationQuery query,
        CancellationToken cancellationToken)
    {
        var notifications = await _service.GetAllAsync(query, cancellationToken);
        var response = _mapper.Map<PaginationModel<NotificationViewModel>>(notifications);

        if (!query.Paginate)
        {
            return Ok(response.Items);
        }

        response.AddRouteLink(Url.RouteUrl(nameof(GetNotifications))!, query);
        return Ok(response);
    }
}

AutoMapper Profile

using Miccore.Pagination;

public class NotificationProfile : Profile
{
    public NotificationProfile()
    {
        CreateMap<Notification, NotificationDto>().ReverseMap();
        CreateMap<NotificationDto, NotificationViewModel>().ReverseMap();
        CreateMap<PaginationModel<Notification>, PaginationModel<NotificationDto>>().ReverseMap();
        CreateMap<PaginationModel<NotificationDto>, PaginationModel<NotificationViewModel>>().ReverseMap();
    }
}

License

MIT License - see LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 116 12/12/2025
1.0.0 17,804 2/26/2022

v2.0.0 - Breaking Changes:
     - Multi-targeting .NET 8.0 and .NET 10.0 (dropped .NET 6.0 support)
     - Namespace unified to Miccore.Pagination
     - PaginationQuery properties now PascalCase (paginate→Paginate, page→Page, limit→Limit)
     - RouterLink class renamed to RouteLink
     - Added sorting support (OrderBy, OrderDirection)
     - Added synchronous Paginate() method
     - Added IAsyncEnumerable streaming methods (AsStreamAsync, PaginateAsStreamAsync)
     - Full nullable reference types support