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):
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.
using System;
public sealed class PaginationConversion
{
#region Properties
public int FirstPage { get; }
public int PageSize { get; }
public int PageCount { get; }
public int NumberOfFirstItemsToSkip { get; }
#endregion //***** Properties
#region Constructors
private PaginationConversion(int firstPage, int pageSize, int pageCount, int numberOfFirstItemsToSkip)
{
FirstPage = firstPage;
PageSize = pageSize;
PageCount = pageCount;
NumberOfFirstItemsToSkip = numberOfFirstItemsToSkip;
}
#endregion //***** Constructors
#region Methods
public static PaginationConversion Convert(int fromCurrentPage, int fromPageSize, int toPageSize)
{
double fromFirstItemIndex = (fromCurrentPage - 1) * fromPageSize + 1;
var toFirstPage = (int)Math.Floor((fromFirstItemIndex - 1) / toPageSize) + 1;
var toFirstItemIndex = (toFirstPage - 1) * toPageSize + 1;
var toFirstPageFirstItemsToSkip = (int) fromFirstItemIndex - toFirstItemIndex;
var toPageCount = (int) Math.Ceiling((fromPageSize - (toPageSize - toFirstPageFirstItemsToSkip)) / toPageSize) + 1;
return new PaginationConversion(
toFirstPage,
toPageSize,
toPageCount,
toFirstPageFirstItemsToSkip);
}
#endregion //***** Methods
}
The following pseudo code shows how to implement the conversion result:
public sealed class DummyRepository
{
private const toPageSize = 36;
public List<SomeEntity> Get(int currentPage, int pageSize)
{
var pagination = PaginationConversion.Convert(sourceCurrentPage: 5, sourcePageSize: 100, targetPageSize: 36);
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++;
}
if (pagination.NumberOfFirstItemsToSkip > 0) result.RemoveRange(0, pagination.NumberOfFirstItemsToSkip);
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:
public class Program
{
public static void Main(string[] args)
{
static List<string> GetPage(IEnumerable<string> source, int currentPage, int pageSize) => source.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
const int fromCurrentPage = 2;
const int fromPageSize = 5;
const int toPageSize = 3;
var dataSource = new List<string>();
for (var i=0; i < 20; i++)
dataSource.Add($"Item {i+1}");
var pagination = PaginationConversion.Convert(fromCurrentPage, fromPageSize, toPageSize);
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++;
}
if (pagination.NumberOfFirstItemsToSkip > 0) result.RemoveRange(0, pagination.NumberOfFirstItemsToSkip);
if (result.Count > fromPageSize) result.RemoveRange(result.Count - (result.Count - fromPageSize), result.Count - fromPageSize);
}
}
The screenshot below shows the outcome of the request:
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.