Paging with RavenDB and ASP.NET MVC

The RavenDB LINQ client supports both Skip and Take functions, making paging through a collection super easy:

int pageSize = 10;
int pageIndex = 2;

var candidates = session.Query<Candidate>()
                .Skip((pageIndex - 1) * pageSize)
                .Take(pageSize)
                .ToList();

Note: The page number requested by the user is normally 1-based, hence the need to do pageIndex - 1.

To add paging to your UI you'll typically need the following information:

  • Page index
  • Page size
  • Total number of items
  • Total Pages
  • Whether there is a previous page
  • Whether there is a next page

Most of the above can be calculated from the total number of query results, which RavenDB conveniently provides via the RavenQueryStatistics object.

RavenQueryStatistics stats;
var candidates = session.Query<Candidate>()
                .Statistics(out stats)
                .Skip((pageIndex - 1) * pageSize)
                .Take(pageSize)
                .ToList();

var totalNumberOfItems = stats.TotalResults;

To encapsulate the above we use the following "PagedList" implementation. In addition to automatically calculating the total number of records (issuing a Count() on the input query/collection) you can also specify it manually, perfect for both NHibernate future queries and of course RavenDB.

public interface IPagedList : IEnumerable
{
    int PageIndex { get; }
    int PageSize { get; }
    int TotalCount { get; }
    int TotalPages { get; }
    bool HasPreviousPage { get; }
    bool HasNextPage { get; }
}

public interface IPagedList<T> : IPagedList, IList<T>
{
}

public class PagedList<T> : List<T>, IPagedList<T>
{
    public PagedList(IEnumerable<T> source, int pageIndex, int pageSize) :
        this(source.GetPage(pageIndex, pageSize), pageIndex, pageSize, source.Count()) { }

    public PagedList(IEnumerable<T> source, int pageIndex, int pageSize, int totalCount)
    {
        this.TotalCount = totalCount;
        this.TotalPages = totalCount / pageSize;

        if (totalCount % pageSize > 0)
            TotalPages++;

        this.PageSize = pageSize;
        this.PageIndex = pageIndex;

        this.AddRange(source.ToList());
    }

    public int PageIndex { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }
    public int TotalPages { get; private set; }

    public bool HasPreviousPage { get { return (PageIndex > 0); } }
    public bool HasNextPage { get { return (PageIndex + 1 < TotalPages); } }
}

Let's look at how we use this in our ASP.NET MVC controllers:

[HttpGet]
public ViewResult Index(int page = 1)
{
    int pageSize = 20;

    RavenQueryStatistics stats;
    var candidates = session.Query<Fabrik.Recruit.Domain.Candidate>()
                        .Statistics(out stats)
                        .OrderBy(c => c.ContactDetails.LastName)
                        .Skip((page - 1) * pageSize)
                        .Take(pageSize)
                        .Select(c => new CandidatesIndexModel.CandidateContactCard
                        {
                            Id = c.Id,
                            Name = c.ContactDetails.FullName,
                        })
                        .ToList();

    var model = new PagedList<CandidatesIndexModel.CandidateContactCard>(
                    candidates, 
                    page - 1, 
                    pageSize, 
                    stats.TotalResults
                );

    return View(model);                      
}

To create the pager in our views I created the following razor helper (twitter bootstrap compatible):

@using System;

@helper If(bool condition, string then) {
    if(condition) { <text>@then</text> }
}

@helper Pager(int currentPage, int totalPages, Func<int, string> pageUrl, string cssClass = null) {
    if (totalPages > 1) { 
    <div class="pagination @cssClass">
        <ul>
            @for (int i = 1; i < totalPages + 1; i++) { 
                <li @If(i == (currentPage + 1), then: "class=active")>
                    <a href="@pageUrl(i)">@i</a>
                </li>
            }
        </ul>
    </div>
    }
}

The helper takes in a Func<int, string> for generating the page link urls. Assuming our view model is a IPagedList we add a pager like so:

@RazorHelpers.Pager(
    Model.PageIndex, 
    Model.TotalPages, 
    x => Url.Action("index", new { page = x })
)

Paging has never been so simple.


Ben Foster

About Me

I'm a software engineer and aspiring entrepreneur with 12+ years experience in the tech industry and have worked with startups and SMB’s in areas such as healthcare, recruitment and e-commerce (I even worked in enterprise, once). I founded my first startup Fabrik in 2011.

I now head up the engineering team at Checkout. If you're interested in working in an exciting and innovative fin-tech company, drop me a message.

Creative Commons Licence