Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Pagination conversion in C#

4.38/5 (5 votes)
10 Jun 2022CPOL2 min read 8.4K  
Easily convert a request for a page between pagination designs (page sizes)
Working with different sources designs for pagination (page size) can differ. The PaginationConversion helper class helps to easily convert a request for a page of data between different pagination designs.

Introduction

Designing pagination seems like a trivial task. It's just choosing a page size (or set of page sizes) and sticking with it. When not using external data sources this works perfectly. However when using different API's returning lists of data you can come across different pagination designs that don't match yours. You could adopt the page size of the different sources, however this would be opaque for your users. So instead you can now keep our own design and use the PaginationConversion helper allowing you to easily convert requests for a page using your pagination design to the pagination of the data source(s) being requested.

Background

The example below illustrates the page overlap between two pagination designs with page size 5 (from) and page size 3 (to):

Image 1

The table should be read as follows:

A request for page 1 of design 5 requires page 1 and the first two items of page 2 in design 3. 

A request for page 2 of design 5 requires the last item of page 2, page 3 and the first item of page 4 in design 3.

Etc.

Using the code

The PaginationConversion class helps us to easily switch from our pagination design to any other design. It uses a Convert factory method requiring the current page and page size for the design converting from and the page size of the design converting to.

C#
using System;

/// <summary>
/// Result for the conversion from one pagination design to another.
/// </summary>
public sealed class PaginationConversion
{
    #region Properties
    
    /// <summary>
    /// First page to request in the to design.
    /// </summary>
    public int FirstPage { get; }

    /// <summary>
    /// Page size of the to design.
    /// </summary>
    public int PageSize { get; }

    /// <summary>
    /// Number of pages required from the to design to fulfill the page size of the from design.
    /// </summary>
    public int PageCount { get; }
    
    /// <summary>
    /// Number of first items to skip from the first page of the to design.
    /// </summary>    
    public int NumberOfFirstItemsToSkip { get; }
    
    #endregion //***** Properties
    
    #region Constructors
    
    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="firstPage">First page to request in the to design</param>
    /// <param name="pageSize">Page size of the to design</param>
    /// <param name="pageCount">Number of pages required from the to design to fulfill the page size of the from design</param>
    /// <param name="numberOfFirstItemsToSkip">Number of first items to skip from the first page of the to design</param>
    private PaginationConversion(int firstPage, int pageSize, int pageCount, int numberOfFirstItemsToSkip)
    {
        FirstPage = firstPage;
        PageSize = pageSize;
        PageCount = pageCount;
        NumberOfFirstItemsToSkip = numberOfFirstItemsToSkip;
    }
    
    #endregion //***** Constructors
        
    #region Methods
    
    /// <summary>
    /// Converts a request for the current page of the from pagination design to the request(s) required for the to pagination design.
    /// </summary>
    /// <param name="fromCurrentPage">Current page in the from paginition design</param>
    /// <param name="fromPageSize">Page size in the from pagination design</param>
    /// <param name="toPageSize">Page size in the to pagination design</param>
    /// <returns>Settings required for the to pagination design</returns>
    public static PaginationConversion Convert(int fromCurrentPage, int fromPageSize, int toPageSize)
    {
        //***** Calculate the (one-based) index of the first item in the from design;
        double fromFirstItemIndex = (fromCurrentPage - 1) * fromPageSize + 1;

        //***** Calculate the first page in the to design to require items from;
        var toFirstPage = (int)Math.Floor((fromFirstItemIndex - 1) / toPageSize) + 1;

        //***** Calculate the (one-based) index of the first item in the to design;
        var toFirstItemIndex = (toFirstPage - 1) * toPageSize + 1;

        //***** Calculate the first items to skip in the first page of the to design;
        var toFirstPageFirstItemsToSkip = (int) fromFirstItemIndex - toFirstItemIndex;

        //***** Calculate the required page count for the to design;
        var toPageCount = (int) Math.Ceiling((fromPageSize - (toPageSize - toFirstPageFirstItemsToSkip)) / toPageSize) + 1;

        //***** Return result;
        return new PaginationConversion(
            toFirstPage,
            toPageSize,
            toPageCount,
            toFirstPageFirstItemsToSkip);
    }    
    
    #endregion //***** Methods
}

The following pseudo code shows how to implement the conversion result:

C#
public sealed class DummyRepository
{
    /// <summary>
    /// Maximum page size in the to design.
    /// </summary>
    private const toPageSize = 36;  

    /// <summary>
    /// Get the items from the to system for the current page and page size of the from design.
    /// </summary>
    /// <param name="currentPage">Current page of the from design</param>
    /// <param name="pageSize">Page size of the from design</param>
    /// <returns>Items from the to system for the current page and page size of the from design</returns>
    public List<SomeEntity> Get(int currentPage, int pageSize)
    {
        //***** Convert from design into to design;
        var pagination = PaginationConversion.Convert(sourceCurrentPage: 5, sourcePageSize: 100, targetPageSize: 36);

        //***** List of results from to system;
        var result = new List<SomeEntity>();
        var index = 0;
        while (index < pagination.PageCount)
        {
            var page = someEntityRepository.Get(pagination.FirstPage + index, pagination.PageSize);
            result.AddRange(page);             
            index++;
        }

        //***** Check if first items need to be skipped. If so, remove first items from result;
        if (pagination.NumberOfFirstItemsToSkip > 0) result.RemoveRange(0, pagination.NumberOfFirstItemsToSkip);

        //***** Check if items in result exceed page size. If so, remove last items from result;
        if (result.Count > pageSize) result.RemoveRange(result.Count - (result.Count - pageSize), result.Count - pageSize);

        //*****
        return result;
    }
}

The while loop iterates the number of pages in the page count and collects the items in the result list. After collecting the items from the source, the first items to skip (if any) and the last items (if the from design page size has been exceeded) are removed, resulting in the from page.

Code sample

The following code sample generates a dummy data source for the to system using a page size of 3. The from system uses a page size of 5:

C#
public class Program
{
    public static void Main(string[] args)
    {
        //***** Helper method for retrieving pages from an enumerable;
        static List<string> GetPage(IEnumerable<string> source, int currentPage, int pageSize) => source.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();

        //***** Pagination designs;
        const int fromCurrentPage = 2;
        const int fromPageSize = 5;
        const int toPageSize = 3;

        //***** Dummy data source ("to" system);
        var dataSource = new List<string>();
        for (var i=0; i < 20; i++)
            dataSource.Add($"Item {i+1}");
        
        //***** Pagination conversion;
        var pagination = PaginationConversion.Convert(fromCurrentPage, fromPageSize, toPageSize);

        //***** Iterate;
        var pages = new List<List<string>>();
        var result = new List<string>();
        var index = 0;
        while (index < pagination.PageCount)
        {
            var page = GetPage(dataSource, pagination.FirstPage + index, pagination.PageSize);
            pages.Add(page);
            result.AddRange(page);
            index++;
        }

        //***** Check if first items need to be skipped. If so, remove first items from result;
        if (pagination.NumberOfFirstItemsToSkip > 0) result.RemoveRange(0, pagination.NumberOfFirstItemsToSkip);

        //***** Check if items in result exceed page size. If so, remove last items from result;
        if (result.Count > fromPageSize) result.RemoveRange(result.Count - (result.Count - fromPageSize), result.Count - fromPageSize);
    }
}

The screenshot below shows the outcome of the request:

Image 2

 

I hope it makes sense to you as it did to me. It was fun writing it anyhow :D.

Cheers!

History

  • 2022-06-10 - Screenshots + code sample. Thanks @WeiminYu 
  • 2022-06-09 - First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)