Table of Contents

Pagination

Returning unbounded result sets from an API is a performance problem waiting to happen. Pagination is the standard solution, but implementing it consistently across all endpoints requires a shared request/response pattern. PaginatedRequest and PaginatedResponse provide that pattern.

PaginatedRequest

PaginatedRequest takes a page number and page size, and automatically calculates the Skip and Take values you need for your data query:

using MADE.Web;

var request = new PaginatedRequest(page: 2, pageSize: 25);
// request.Skip = 25
// request.Take = 25

PaginatedResponse

PaginatedResponse<T> wraps the result set with metadata about the current page and total available data:

using MADE.Web;

var response = new PaginatedResponse<Product>(
    items: products,
    page: 2,
    pageSize: 25,
    availableCount: 150);

// response.Page = 2
// response.PageSize = 25
// response.AvailableCount = 150
// response.TotalPages = 6

The TotalPages property is calculated automatically from AvailableCount and PageSize.

Using with EF Core

Pair with the Page extension from MADE.Data.EFCore for a complete pagination flow:

using MADE.Data.EFCore.Extensions;
using MADE.Web;

[HttpGet("products")]
public async Task<PaginatedResponse<ProductDto>> GetProducts(
    [FromQuery] int page = 1,
    [FromQuery] int pageSize = 25)
{
    var totalCount = await dbContext.Products.CountAsync();

    var items = await dbContext.Products
        .OrderBy(p => p.Name)
        .Page(page, pageSize)
        .Select(p => new ProductDto(p.Id, p.Name, p.Price))
        .ToListAsync();

    return new PaginatedResponse<ProductDto>(items, page, pageSize, totalCount);
}

The response gives the client everything it needs to build pagination UI: the current items, which page they're on, and how many pages are available.

Query collection helpers

The MADE.Web.Extensions.QueryCollectionExtensions class provides helpers for reading typed values from query strings:

using MADE.Web.Extensions;

int page = context.Request.Query.GetIntValueOrDefault("page", 1);
string search = context.Request.Query.GetStringValueOrDefault("q", "");
DateTime? from = context.Request.Query.GetDateTimeValueOrDefault("from", null);

These return the specified default value when the query parameter is missing or can't be parsed, avoiding the boilerplate of manual parsing and null checking.