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);
}
}
}