Skip to content

Pagination

In large scale systems, pagination is an important aspect of web API design, as it helps ensure that the API is scalable and efficient, and can handle a high volume of requests and data. Pagination is the practice of dividing large data sets into smaller, manageable chunks, and providing links to navigate through the data.

List Results

It is common to return a list of DTOs to the client. Having IListResult interface and ListResultDto class is best way to make it standard.

public interface IListResult<T>
{
    IReadOnlyList<T> Items { get; set; }
}
[Serializable]
public class ListResultDto<T> : IListResult<T>
{
    public IReadOnlyList<T> Items
    {
        get { return _items ?? (_items = new List<T>()); }
        set { _items = value; }
    }
    private IReadOnlyList<T>? _items;

    public ListResultDto()
    {

    }

    public ListResultDto(IReadOnlyList<T> items)
    {
        Items = items;
    }
}

Therefore you can later add more properties to your return value without breaking your remote clients (when they get the value as a JSON result). Automapper is used for mapping entities at below example.

public virtual async Task<ListResultDto<OdmsUserDto>> GetUserLookupAsync()
{ 
    # Get entities from the repository
    var users = await UserRepository.GetListAsync();

    # Map entities to DTOs and return result.
    return new ListResultDto<OdmsUserDto>(
        ObjectMapper.Map<List<OdmsUser>, List<OdmsUserDto>>(users)
    );
}

Paged & Sorted List Results

It is more common to request a paged list from server and return a paged list to the client.

In order to create a standard way, at least the following input parameters must be available at request.

  • Define a MaxResultCount (int) property to request a limited result from the server. Set default page size is 10. This value should be changed by setting the static properties. If the client sends MaxResultCount greater than 1,000 a validation error must occur. It is important to protect the server from abuse of the service.
public interface ILimitedResultRequest
{
    int MaxResultCount { get; set; }
}

Use System.ComponentModel.DataAnnotations for basic validation operations.

[Serializable]
public class LimitedResultRequestDto : ILimitedResultRequest, IValidatableObject
{
    public static int DefaultMaxResultCount { get; set; } = 10;

    public static int MaxMaxResultCount { get; set; } = 1000;

    [Range(1, int.MaxValue)]
    public virtual int MaxResultCount { get; set; } = DefaultMaxResultCount;

    public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (MaxResultCount > MaxMaxResultCount)
        {
            yield return new ValidationResult(
                "Type here max result exception message",
                new[] { nameof(MaxResultCount) });
        }
    }
}
  • Define a SkipCount (int) to declare the skip count while requesting a paged result from the server.
public interface IPagedResultRequest : ILimitedResultRequest
{
    int SkipCount { get; set; }
}
[Serializable]
public class PagedResultRequestDto : LimitedResultRequestDto, IPagedResultRequest
{
    [Range(0, int.MaxValue)]
    public virtual int SkipCount { get; set; }
}
  • Define a Sorting (string) property to request a sorted result from the server. Sorting value can be "Name", "Name DESC", "Name ASC, Age DESC"... etc.
public interface ISortedResultRequest
{
    string? Sorting { get; set; }  # (ASC or DESC) Can contain more than one field separated by comma (,).
}
public interface IPagedAndSortedResultRequest : IPagedResultRequest, ILimitedResultRequest, ISortedResultRequest
{
}
[Serializable]
public class PagedAndSortedResultRequestDto : PagedResultRequestDto, IPagedAndSortedResultRequest
{
    public virtual string? Sorting { get; set; }
}

The following parameters must be available to standardize the response sent to the clients.

  • Inherit from the standardized response DTO class for the paged response.
  • Define a TotalCount (long) property to return the total count of the records in case of paging.
public interface IHasTotalCount
{
    long TotalCount { get; set; }
}
public interface IPagedResult<T> : IListResult<T>, IHasTotalCount
{
}
[Serializable]
public class PagedResultDto<T> : ListResultDto<T>, IPagedResult<T>
{
    public long TotalCount { get; set; }

    public PagedResultDto()
    {

    }

    public PagedResultDto(long totalCount, IReadOnlyList<T> items)
        : base(items)
    {
        TotalCount = totalCount;
    }
}