17

I am trying to implement pagination on .NET Core RESTful API's (with EF).

Rather than re-inventing the wheel, I was hoping there was a way to either use a generic function that could hook into the API handler and intercept requests to apply them to EF Query results or something built into .NET Core that I am un-aware of. Can anyone point me in the general direction of a library that they are aware of that does this in .NET Core

The way I would previously do this (and have done in non .NET Core apps) is to make a function that I have to physically add the parameters to the controller function (pageSize, pageNumber) which does get tedious (and I think a tad untidy) to add these two parameters to every single function.

2
  • 3
    EF has two usefull methods fot that: Skip(int) and Take(int). You can combine both to get only the page you need. Commented May 2, 2017 at 10:52
  • As far as I know there is nothing built-in. But the way I always did it was via a class which is returned from repositories (when using repository pattern for simple applications), which implements one or two interfaces (for sorting, pagination). This way DbContext/IQueryable interface doesn't have to leak into the domain or application layer. With CQRS it's wholly different, as you would have command/queries for a specific handler Commented May 2, 2017 at 10:52

3 Answers 3

22

There is no built-in feature for pagination, and if there was, you would not like it. Imagine a controller method returning 1.000.000 results for pagination just to pick 10 from them. It is up to you to implement the pagination.

The tedious and untidy controller methods like

public class FooController : Controller
{
    public IEnumerable<Foo> GetAll( 
        string Filter, 
        string Whatever, 
        ..., 
        int pageNumber = 1, 
        int pageSize = 20 ) 
    { ... }
}

can be reorganized to

public class FooController : Controller
{
    public IEnumerable<Foo> GetAll( GetAllArgs args ) 
    {
        IQueryable<Foo> query = ...

        return query.Paginate( args ).ToList();  
    }

    public class GetAllArgs : QueryArgsBase
    {
        public string Filter { get; set; }
        public string Whatever { get; set; }
    }
}

public interface IPaginationInfo
{ 
    int PageNumber { get; }
    int PageSize { get; }
}

public abstract class QueryArgsBase : IPaginationInfo
{
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

public static class QueryableExtensions
{
    public static IQueryable<T> Paginate<T>( 
        this IQueryable<T> source, 
        IPaginationInfo pagination )
    {
        return source
            .Skip( ( pagination.PageNumber - 1 ) * pagination.PageSize )
            .Take( pagination.PageSize );
    }
}

Change any other controller method to have such an argument class and inherite from QueryArgsBase or implement IPaginationInfo to use the QueryableExtensions.Paginate method.

Sign up to request clarification or add additional context in comments.

1 Comment

where should I put pagination logic if I use service layer that I call from my controllers?
6

Here's a ready-to-use code based on Sir Rufo's answer:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace ProjectName.Utilities
{
    public static class Extensions
    {
        public static async Task<PaginatedResult<T>> paginate<T>(this IQueryable<T> source,
                                                int pageSize, int pageNumber)
        {
            return await new PaginatedResult<T>(pageNumber, pageSize).paginate(source);
        }
    }

    public class PaginatedResult<T> : ActionResult
    {
        private const int defaultPageSize = 20;
        private const int maxPageSize = 50;

        public int total { get; private set; }
        public int limit { get; private set; }
        public int page { get; private set; }
        public List<T> objects { get; private set; }

        internal PaginatedResult(int pageNumber, int pageSize = defaultPageSize)
        {
            limit = pageSize;
            page = pageNumber;

            if (limit < 0 || limit > maxPageSize)
            {
                limit = defaultPageSize;
            }
            if (pageNumber < 0)
            {
                page = 0;
            }
        }

        internal async Task<PaginatedResult<T>> paginate(IQueryable<T> queryable)
        {
            total = queryable.Count();

            if (limit > total)
            {
                limit = total;
                page = 0;
            }

            int skip = page * limit;
            if (skip + limit > total)
            {
                skip = total - limit;
                page = total / limit - 1;
            }

            objects = await queryable.Skip(skip).Take(limit).ToListAsync();
            return this;
        }
    }
}


And in your controller:

// ...
[HttpGet]
public async Task<ActionResult<PaginatedResult<MyDataType>>> getMyData(int pageSize = 20,
                                                                       int pageNumber = 0)
{
    return await _context.myData.AsNoTracking().paginate(pageSize, pageNumber);
}
// ...

Comments

1

Like you I have had this problem that There is no built-in feature for pagination in .NET 😕, so several years ago I created a nice package for myself and shared it with everyone that want use it! So, if you want make life easier😎, you could simply install my pagination Nugget Package: RaminBateni.Utils.Pagination. 🤩

Steps:

  1. Go to nugget packages of your project in Visual Studio. Then search RaminBateni.Utils.Pagination and Install it. That's it!

  2. Now, use it like the bellow codes and simply pass the page number and page size that you want:

using RaminBateni.Utils.Pagination;


var pagedResultQuery = _context.Set<Label>()
      .Include(x => x.Article)
      .Where(x => x.CollectionId == 2)
      .OrderBy(x=> x.Labeld)
      .AsPagedToListAsync(page: 5, pageSize: 20);  //  <<-- The Magic Line

// Good news:
// It does not fetch any records from database until you `await` it ;)

var pagedResult = await pagedResultQuery(); 

  1. Result:

You will get an object result like this (it contains everything that client probably needs😉):

{
   List<T> Items
   int     CurrentPage
   int     PageCount
   int     PageSize
   int     RowCount
   int     FirstRowOnPage
   int     LastRowOnPage
}

Source repository: https://github.com/Ramin-Bateni/RaminBateni.Utils.Pagination

If you have any question let me know. Please share it with your other coder friends and star it on GitHub if you liked it.

Great .NET Pagination Package

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.