Return of the PagedList
// June 15th, 2009 // C#, LINQ, MVC
It has been nearly a year since I posted an updated version of the PagedList<T> functionality originally created by Scott Guthrie and posted by Rob Conery. Since then I have used the class in a number of projects and find it indispensable.
A few days ago, Craig Stuntz reported an interesting observation: when the first page is returned, the class performs a Skip(0). Suprisingly, this is not free. With that in mind, I set out to correct that issue as well as incorporate a few changes I’ve made over the past year. The result is nearly identical to the last posted version, just a bit more readable. Additionally…
- The source is now available on CodePlex: http://pagedlist.codeplex.com. This should make finding and downloading the code easier than finding the correct blog entry on some dude’s blog.
- I have posted a release-compiled, XML commented, signed assembly on CodePlex. I got tired of having to copy the source into multiple projects and finding a place to put it in that project’s taxonomy.
- Further incremental changes can be found in the Change Log on the CodePlex project site.
Download from CodePlex
IPagedList<T>.cs
using System.Collections.Generic;
namespace PagedList
{
public interface IPagedList<T> : IList<T>
{
int PageCount { get; }
int TotalItemCount { get; }
int PageIndex { get; }
int PageNumber { get; }
int PageSize { get; }
bool HasPreviousPage { get; }
bool HasNextPage { get; }
bool IsFirstPage { get; }
bool IsLastPage { get; }
}
}
PagedList<T>.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace PagedList
{
public class PagedList<T> : List<T>, IPagedList<T>
{
public PagedList(IEnumerable<T> superset, int index, int pageSize)
{
// set source to blank list if superset is null to prevent exceptions
var source = superset == null
? new List<T>().AsQueryable()
: superset.AsQueryable();
TotalItemCount = source.Count();
PageSize = pageSize;
PageIndex = index;
if (TotalItemCount > 0)
PageCount = (int) Math.Ceiling(TotalItemCount/(double) PageSize);
else
PageCount = 0;
if (index < 0)
throw new ArgumentOutOfRangeException("index", index, "PageIndex cannot be below 0.");
if (pageSize < 1)
throw new ArgumentOutOfRangeException("pageSize", pageSize, "PageSize cannot be less than 1.");
// add items to internal list
if (TotalItemCount > 0)
if (index == 0)
AddRange(source.Take(pageSize).ToList());
else
AddRange(source.Skip((index) * pageSize).Take(pageSize).ToList());
}
public int PageCount { get; private set; }
public int TotalItemCount { get; private set; }
public int PageIndex { get; private set; }
public int PageSize { get; private set; }
public int PageNumber
{
get { return PageIndex + 1; }
}
public bool HasPreviousPage
{
get { return PageIndex > 0; }
}
public bool HasNextPage
{
get { return PageIndex < (PageCount - 1); }
}
public bool IsFirstPage
{
get { return PageIndex <= 0; }
}
public bool IsLastPage
{
get { return PageIndex >= (PageCount - 1); }
}
}
}
PagedListExtensions.cs
using System.Collections.Generic;
using System.Linq;
namespace PagedList
{
public static class PagedListExtensions
{
public static IPagedList<T> ToPagedList<T>(this IEnumerable<T> superset, int index, int pageSize)
{
return new PagedList<T>(superset, index, pageSize);
}
}
}
PagedListFacts.cs
using System;
using System.Collections.Generic;
using Xunit;
using Xunit.Extensions;
namespace PagedList.Tests
{
public class PagedListFacts
{
[Fact]
public void Null_Data_Set_Doesnt_Throw_Exception()
{
//act
Assert.ThrowsDelegate act = () => new PagedList<object>(null, 0, 10);
//assert
Assert.DoesNotThrow(act);
}
[Fact]
public void PageIndex_Below_Zero_Throws_ArgumentOutOfRange()
{
//arrange
var data = new[] {1, 2, 3};
//act
Assert.ThrowsDelegate act = () => data.ToPagedList(-1, 1);
//assert
Assert.Throws<ArgumentOutOfRangeException>(act);
}
[Fact]
public void PageIndex_Above_RecordCount_Returns_Empty_List()
{
//arrange
var data = new[] {1, 2, 3};
//act
var pagedList = data.ToPagedList(2, 3);
//assert
Assert.Equal(0, pagedList.Count);
}
[Fact]
public void PageSize_Below_One_Throws_ArgumentOutOfRange()
{
//arrange
var data = new[] {1, 2, 3};
//act
Assert.ThrowsDelegate act = () => data.ToPagedList(0, 0);
//assert
Assert.Throws<ArgumentOutOfRangeException>(act);
}
[Fact]
public void Null_Data_Set_Doesnt_Return_Null()
{
//act
var pagedList = new PagedList<object>(null, 0, 10);
//assert
Assert.NotNull(pagedList);
}
[Fact]
public void Null_Data_Set_Returns_Zero_Pages()
{
//act
var pagedList = new PagedList<object>(null, 0, 10);
//assert
Assert.Equal(0, pagedList.PageCount);
}
[Fact]
public void Zero_Item_Data_Set_Returns_Zero_Pages()
{
//arrange
var data = new List<object>();
//act
var pagedList = data.ToPagedList(0, 10);
//assert
Assert.Equal(0, pagedList.PageCount);
}
[Fact]
public void DataSet_Of_One_Through_Five_PageSize_Of_Two_PageIndex_Of_One_First_Item_Is_Three()
{
//arrange
var data = new[] {1, 2, 3, 4, 5};
//act
var pagedList = data.ToPagedList(1, 2);
//assert
Assert.Equal(3, pagedList[0]);
}
[Fact]
public void TotalCount_Is_Preserved()
{
//arrange
var data = new[] {1, 2, 3, 4, 5};
//act
var pagedList = data.ToPagedList(1, 2);
//assert
Assert.Equal(5, pagedList.TotalItemCount);
}
[Fact]
public void PageIndex_Is_Preserved()
{
//arrange
var data = new[] {1, 2, 3, 4, 5};
//act
var pagedList = data.ToPagedList(1, 2);
//assert
Assert.Equal(1, pagedList.PageIndex);
}
[Fact]
public void PageSize_Is_Preserved()
{
//arrange
var data = new[] {1, 2, 3, 4, 5};
//act
var pagedList = data.ToPagedList(1, 2);
//assert
Assert.Equal(2, pagedList.PageSize);
}
[Fact]
public void Data_Is_Filtered_By_PageSize()
{
//arrange
var data = new[] {1, 2, 3, 4, 5};
//act
var pagedList = data.ToPagedList(1, 2);
//assert
Assert.Equal(2, pagedList.Count);
//### related test below
//act
pagedList = data.ToPagedList(2, 2);
//assert
Assert.Equal(1, pagedList.Count);
}
[Fact]
public void DataSet_OneThroughSix_PageSize_Three_PageIndex_Zero_FirstValue_Is_One()
{
//arrange
var data = new[] { 1, 2, 3, 4, 5, 6 };
//act
var pagedList = data.ToPagedList(0, 3);
//assert
Assert.Equal(1, pagedList[0]);
}
[Fact]
public void DataSet_OneThroughThree_PageSize_One_PageIndex_Two_HasNextPage_False()
{
//arrange
var data = new[] {1, 2, 3};
//act
var pagedList = data.ToPagedList(2, 1);
//assert
Assert.Equal(false, pagedList.HasNextPage);
}
[Fact]
public void DataSet_OneThroughThree_PageSize_One_PageIndex_Two_IsLastPage_True()
{
//arrange
var data = new[] {1, 2, 3};
//act
var pagedList = data.ToPagedList(2, 1);
//assert
Assert.Equal(true, pagedList.IsLastPage);
}
[Fact]
public void DataSet_OneAndTwo_PageSize_One_PageIndex_One_FirstValue_Is_Two()
{
//arrange
var data = new[] { 1, 2 };
//act
var pagedList = data.ToPagedList(1, 1);
//assert
Assert.Equal(2, pagedList[0]);
}
[Theory]
[InlineData(new[] {1, 2, 3}, 0, 1)]
[InlineData(new[] {1, 2, 3}, 1, 2)]
[InlineData(new[] {1, 2, 3}, 2, 3)]
public void Theory_PageNumber_Is_PageIndex_Plus_One(int[] integers, int pageIndex, int expectedPageNumber)
{
//arrange
var data = integers;
//act
var pagedList = data.ToPagedList(pageIndex, 1);
//assert
Assert.Equal(expectedPageNumber, pagedList.PageNumber);
}
[Theory]
[InlineData(new[] {1, 2, 3}, 0, 1, false, true)]
[InlineData(new[] {1, 2, 3}, 1, 1, true, true)]
[InlineData(new[] {1, 2, 3}, 2, 1, true, false)]
public void Theory_HasPreviousPage_And_HasNextPage_Are_Correct(int[] integers, int pageIndex, int pageSize,
bool expectedHasPrevious, bool expectedHasNext)
{
//arrange
var data = integers;
//act
var pagedList = data.ToPagedList(pageIndex, pageSize);
//assert
Assert.Equal(expectedHasPrevious, pagedList.HasPreviousPage);
Assert.Equal(expectedHasNext, pagedList.HasNextPage);
}
[Theory]
[InlineData(new[] {1, 2, 3}, 0, 1, true, false)]
[InlineData(new[] {1, 2, 3}, 1, 1, false, false)]
[InlineData(new[] {1, 2, 3}, 2, 1, false, true)]
public void Theory_IsFirstPage_And_IsLastPage_Are_Correct(int[] integers, int pageIndex, int pageSize,
bool expectedIsFirstPage, bool expectedIsLastPage)
{
//arrange
var data = integers;
//act
var pagedList = data.ToPagedList(pageIndex, pageSize);
//assert
Assert.Equal(expectedIsFirstPage, pagedList.IsFirstPage);
Assert.Equal(expectedIsLastPage, pagedList.IsLastPage);
}
[Theory]
[InlineData(new[] {1, 2, 3}, 1, 3)]
[InlineData(new[] {1, 2, 3}, 3, 1)]
[InlineData(new[] {1}, 1, 1)]
[InlineData(new[] {1, 2, 3}, 2, 2)]
[InlineData(new[] {1, 2, 3, 4}, 2, 2)]
[InlineData(new[] {1, 2, 3, 4, 5}, 2, 3)]
public void Theory_PageCount_Is_Correct(int[] integers, int pageSize, int expectedNumberOfPages)
{
//arrange
var data = integers;
//act
var pagedList = data.ToPagedList(0, pageSize);
//assert
Assert.Equal(expectedNumberOfPages, pagedList.PageCount);
}
}
}




Skip(0) will also not be a no-op in LINQ to SQL in 4.0 – special casing it caused all sorts of subtle odd behavior not least of all people thinking queries were valid because they worked on the first page only to find they broke when they hit the second.
[)amien
Ah, interesting. Thanks for the background info Damien.
[...] Return of the PagedList | SquaredRoot [...]
Return of the PagedList | SquaredRoot…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
Will there be a PagedList baked into ASP.NET MVC 2 ? If so, I hope they grab this project….
Hey Troy! this is really brilliant, well done!! I know this is MVC related, but I should still be able to use this in a Winforms app, right? I know its a silly question to ask, but I have a VERy huge result set, that has to be paged.
Also, is it possibe to implement sorting?
Hi,
How can we implement this with Ajax? Can you show me?
Thanks.
Not able To Use this Using Strongly-Type Helpers in MVC 2 RTM
@Pure Krome: that would’ve been nice, but as we can see – no, it wasn’t added into MVC2
@magz: yes, it’ll work just fine in WinForms
@Kim Cu Hoang: assuming you’re using MVC, you can return the PagedList wrapped by a JsonResult. that should get you down the right path…
@coolgu97: can you elaborate?