Monday, January 20, 2014

Serializable PagedList for .NET

Developers have to page through data sets every day. For example: bring me back 101-200 (or the second page) of 1,000 results. So how do we move that data between our data, service, and UI and layers? That is where a PagedList collection comes in!

The sooner you add this to your core library the better off your whole team will all be. No more creating extra models to hold page data, no more loading extra results just to pass that data around and then not consume it, and no more reinventing the wheel over and over! I really wish that Microsoft would add a native paged list to the .NET framework; but until that time we just have to roll our own.

So what are the qualities of a good PagedList?

  • It should be generic collection.
  • It should support a non generic interface.
  • It should be easy to serialize.

Yes, there is already a PagedList project on NuGet and GitHub. Please do not misunderstand me, that is a good project! However the code below is a bit more light weight and easier to serialize. Additionally I prefer the extension methods of ToPagedList and TakePage; where the former creates a list as a page, and the latter selects a page from a super-set.

But hey, you can decide which you prefer! :)

Interfaces

public interface IPagedList
{
    ICollection Items { get; }
    int Count { get; }
    int PageIndex { get; }
    int PageSize { get; }
    int TotalCount { get; }
    int TotalPages { get; }
    bool HasPreviousPage { get; }
    bool HasNextPage { get; }
}
 
public interface IPagedList<T>: IPagedList
{
    new ICollection<T> Items { get; }
}

Implementation

public sealed class PagedList<T> : IPagedList<T>
{
    public PagedList(
        ICollection<T> items, 
        int pageIndex, 
        int pageSize, 
        int totalCount)
    {
        Items = items;
        PageIndex = pageIndex;
        PageSize = pageSize;
        TotalCount = totalCount;
    }
 
    public ICollection<T> Items { get; private set; }
    public int Count { get { return Items.Count; } }
 
    public int PageIndex { get; private set; }
    public int PageSize { get; private set; }
 
    public int TotalCount { get; private set; }
    public int TotalPages { get { return ((TotalCount - 1) / PageSize) + 1; } }
 
    public bool HasPreviousPage { get { return PageIndex > 0; } }
    public bool HasNextPage { get { return (TotalPages - 1) > PageIndex; } }
        
    ICollection IPagedList.Items
    {
        get { return (ICollection) Items; }
    }
}

Extension Methods

public static class CollectionExtensions
{
    public static IPagedList<T> ToPagedList<T>(
        this ICollection<T> items, 
        int pageIndex, 
        int pageSize, 
        int totalCount)
    {
        return new PagedList<T>(items, pageIndex, pageSize, totalCount);
    }
 
    public static IPagedList<T> TakePage<T>(
        this ICollection<T> items, 
        int pageIndex, 
        int pageSize)
    {
        var collection = items.Skip(pageIndex*pageSize).Take(pageSize).ToArray();
        return new PagedList<T>(collection, pageIndex, pageSize, items.Count);
    }
}

Unit Tests

[Theory]
[InlineData(new[] { 1, 2, 3 }, 0, 5, 3, 1, false, false)]
[InlineData(new[] { 1, 2, 3, 4, 5 }, 0, 5, 5, 1, false, false)]
[InlineData(new[] { 6, 7, 8 }, 1, 5, 8, 2, true, false)]
[InlineData(new[] { 6, 7, 8, 9, 0 }, 1, 5, 10, 2, true, false)]
[InlineData(new[] { 6, 7, 8, 9, 0 }, 1, 5, 12, 3, true, true)]
public void PagedListTests(
    int[] items, 
    int pageIndex, 
    int pageSize, 
    int totalCount, 
    int totalPages, 
    bool hasPreviousPage, 
    bool hasNextPage)
{
    var pagedList = new PagedList<int>(items, pageIndex, pageSize, totalCount);
 
    Assert.Equal(items.Length, pagedList.Count);
    Assert.Equal(pageIndex, pagedList.PageIndex);
    Assert.Equal(pageSize, pagedList.PageSize);
    Assert.Equal(totalCount, pagedList.TotalCount);
    Assert.Equal(totalPages, pagedList.TotalPages);
    Assert.Equal(hasPreviousPage, pagedList.HasPreviousPage);
    Assert.Equal(hasNextPage, pagedList.HasNextPage);
 
    var json = JsonConvert.SerializeObject(pagedList, Formatting.Indented);
    var deserialized = JsonConvert.DeserializeObject<PagedList<int>>(json);
 
    Assert.Equal(items.Length, deserialized.Count);
    Assert.Equal(pageIndex, deserialized.PageIndex);
    Assert.Equal(pageSize, deserialized.PageSize);
    Assert.Equal(totalCount, deserialized.TotalCount);
    Assert.Equal(totalPages, deserialized.TotalPages);
    Assert.Equal(hasPreviousPage, deserialized.HasPreviousPage);
    Assert.Equal(hasNextPage, deserialized.HasNextPage);
}
Shout it

Enjoy,
Tom

1 comment:

Real Time Web Analytics