Paging In .Net Core with C# and Linq

Almost every medium to large site requires some sort of paging through lists of information. The advantage of paging is that you only need to bring back a limited result set. The user might not care about the results on the last page so only bring back the pages requested and reduce the overall imapact on your database.

I use this method with front end frameworks such as Vue, Angular, React.js. Rather than paging with numbers and using a next/previous link I prefer going with a 'load more' approach on scroll. However this backend technique can be used in both scenarios

Create a PagedList class:

public class PagedList<T> : List<T>
{
    public int CurrentPage { get; private set; }
    public int TotalPages { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }

    public bool HasPrevious
    {
        get { return (CurrentPage > 1); }
    }

    public bool HasNext
    {
        get { return (CurrentPage < TotalPages); }
    }

    public PagedList(List<T> items, int count, int pageNumber, int pageSize)
    {
        TotalCount = count;
        PageSize = pageSize;
        CurrentPage = pageNumber;
        TotalPages = (int)Math.Ceiling(count / (double)pageSize);
        AddRange(items);
    }

    public static async Task<PagedList<T>> CreateAsync(
        IQueryable<T> source,
        int pageNumber,
        int pageSize
    )
    {
        var count = await source.CountAsync();
        var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
        return new PagedList<T>(items, count, pageNumber, pageSize);
    }
}

Create a PageMetaData class. This will be sent in the API response

public class PageMetaData
{
    public int CurrentPage { get; private set; }
    public int TotalPages { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }

    public int? NextPage => CurrentPage+1;

    public bool HasPrevious
    {
        get
        {
            return (CurrentPage > 1);
        }
    }

    public bool HasNext
    {
        get
        {
            return (CurrentPage < TotalPages);
        }
    }
}

Create a ResourceParameters class. Resource Parameters can be passed to your Api as query parameters

public class ResourceParameters
{
    const int maxPageSize = 20;
    public int PageNumber { get; set; } = 1;

    private int _pageSize = 10;
    public int PageSize
    {
        get { return _pageSize; }
        set { _pageSize = (value > maxPageSize) ? maxPageSize : value; }
    }
}

Service to query the database:

public async Task <PagedList<Post> GetPosts(ResourceParameters resourceParameters) 
{
    var collection = ApplicationDbContext.Posts
                    .OrderByDescending(x = >x.DateCreated)
                    .AsQueryable();

    var posts = await PagedList<Post>.CreateAsync(collection, resourceParameters.PageNumber, resourceParameters.PageSize);
    return posts;
}

If using AutoMapper, this PageMetaData can me mapped with a profile

public class PostProfile : Profile
{
    public PostProfile()
    {
        CreateMap<Post, PostViewModel>();

        CreateMap<PagedList<Post>, PagedList<PostViewModel>>();

        CreateMap<PagedList<Post>, PageMetaData>();
    }
}

An example API call with the paging properties passed as query parameters

[HttpGet()]
public async Task <IActionResult> GetPosts([FromQuery] ResourceParameters resourceParameters) 
{
    var postsPagedList = await _unitOfWork.Posts.GetPosts(resourceParameters);
    var posts = _mapper.Map <IEnumerable<PostViewModel>> (postsPagedList);
    var pageMetaData = _mapper.Map<PageMetaData>(postsPagedList);

    var response = new PostListResult() {
    	Posts = posts,
    	PageMetaData = pageMetaData
    };
    return Ok(response);
}