AutoMapperPagedList

9 Jun

If you’re just looking for an example of how to use AutoMapperPagedList, drop down to the bottom and look at the last code sample.

A couple months ago (while at MIX 11, actually) I was playing with some of the new MVC3 Tooling, including EF4.1. To play with it, I was trying to build a simple blog, and for no real reason at all decided to use both AutoMapper and Scott Guthrie’s PagedList. For those unfamiliar with either, AutoMapper is a great way to transform your data access objects into view models, and PagedList makes it really simple to hook up an IQueryable to server-side paging.

First, an explanation of AutoMapper. In creating AutoMapper, Jimmy Bogard provided a super-useful way to automatically map one object to another type. The mapping is primarily based on conventions, so if you name the properties on the classes properly, you only have to do minimal configuration. For more details, see the examples on AutoMapper’s CodePlex site. For my simple blog, I wanted to use AutoMapper to flatten my Post model into a PostViewModel. Because PostViewModel was set up to easily work with AutoMapper, the only configuration I had to do was make sure that this line was called in my Global.asax:

AutoMapper.Mapper.CreateMap<Post, PostViewModel>();

Then when I want to convert a Post to a PostViewModel, I can just call this:

AutoMapper.Mapper.Map<Post, PostViewModel>(post);

Next, an explanation of PagedList. The Gu created a helpful class and extension methods for working with server-side paging. The extension method extends an IQueryable<T> and converts it to a PagedList<T>. PagedList<T> inherits from List<T>, so it has all the normal features of a List<T>, but it adds the interface IPagedList:

public interface IPagedList {
	int TotalCount { get; set; }
	int PageIndex { get; set; }
	int PageSize { get; set; }
	bool IsPreviousPage { get; }
	bool IsNextPage { get; }
}

The properties added by IPagedList can be used in your view to easily control Previous/Next buttons. Additionally, the constructor of PagedList calls Skip() and Take() on the IQueryable before calling ToList() and adding the resultant items to itself, meaning that the execution of the IQueryable happens inside of PagedList. This is important because AutoMapper won’t work as part of deferred execution, which is what is going on with the IQueryable. This means that the trying to use AutoMapper to map an IQueryable<Post> to an IQueryable<PostViewModel> just can’t happen.

So since I can’t use AutoMapper to send objects to PagedList, I had to use AutoMapper inside PagedList, after the IQueryable was executed and returned as a List. However, I didn’t want to muck around too much with the regular PagedList class, so I created a new class that inherits from it instead. This new class is called (quite creatively) MappedPagedList. It works the exact same way as PagedList, but takes one additional generic type and one additional parameter. It requires two generic types because AutoMapper needs a source type and destination type. The extra parameter, however, is the key. As parameter types go, it’s a doozy:

public static MappedPagedList<TSource, TOutput> ToPagedList<TSource, TOutput>(this IQueryable<TSource> source, int index, Func<IEnumerable<TSource>, IEnumerable<TOutput>> mapper, int pageSize = 10) {
	return new MappedPagedList<TSource, TOutput>(source, index, pageSize, mapper);
}

Let me break it down a little.
Func<IEnumerable<TSource>, IEnumerable<TOutput>>
looks intimidating, but it’s really just a delegate for a call to AutoMapper.Map<TSource, TOutput>(). In simpler terms, the parameter takes a method that has one parameter of type IEnumerable<TSource> and returns IEnumerable<TOutput>. The best way to understand it is probably to see an example of the method being called:

IQueryable<Post> posts = context.Posts.OrderByDescending(p => p.Timestamp).AsQueryable();
<PostViewModel> pagedList = posts.ToPagedList<Post, PostViewModel>(2, Mapper.Map<IEnumerable<Post>, IEnumerable<PostViewModel>>, 10);
return View(pagedList);

Because MappedPagedList<T,O> inherits from PagedList<T> (and PagedList<T> is the only part that we actually need in the view), the View only needs to be passed an instance of Paged<T>. The magic happens inside the constructor of MappedPagedList, where it uses the passed-in Func to map the List<T> to a List<O>.

If you’ve read this far, you’re probably getting desperate for the part where I say “…and here’s how you use it in your site”, so here you go.

//Controller
public ViewResult Index(int page = 0) {
	int pageSize = 10;
	IQueryable<Post> posts = context.Posts.OrderByDescending(p => p.Timestamp).AsQueryable();
	PagedList<PostViewModel> pagedList = posts.ToPagedList<Post, PostViewModel>(page, Mapper.Map<IEnumerable<Post>, IEnumerable<PostViewModel>>, pageSize);
	return View(pagedList);
}

//View
@if (Model.IsPreviousPage) {
	@Html.ActionLink("Previous", "Index", new { page = Model.PageIndex - 1 })
}
@(Model.PageIndex + 1) of @(Model.TotalCount / Model.PageSize)
@if (Model.IsNextPage) {
	@Html.ActionLink("Next", "Index", new { page = Model.PageIndex + 1 })
}

This will generate a previous button, a page indicator, a next button. The previous and next button are hidden if there isn’t a previous or next page. The controller takes page as an optional parameter, and passes that to the MappedPagedList to use in Skip().

So why would you want to use it? If you need to use AutoMapper to send view models to your view, but want super-easy paging support, this single file will save you a bunch of time and effort. I’ve put it on NuGet at http://nuget.org/List/Packages/AutoMapperPagedList, so feel free to pull it down and give it a try. It’s a single file, and the source is hosted on GitHub at https://github.com/davecowart/AutoMapperPagedList, so feel free to clone it, fork it, or do whatever. I also have an example MVC project on GitHub at https://github.com/davecowart/AutoMapperPagedList.Demo if you need a full-site example.

Advertisements

3 Responses to “AutoMapperPagedList”

  1. Ricardo Francisco June 10, 2011 at 5:42 PM #

    Very nice. Thanks for this.

    • Dave Cowart June 10, 2011 at 10:01 PM #

      No problem! Glad you found it useful.

  2. Troy Goode (@TroyGoode) August 15, 2011 at 8:46 PM #

    FYI: My own PagedList implementation – which contains many improvements over the Gu’s original – now supports AutoMapper as well (although not quite as cleanly as I chose not to take a dependency upon AutoMapper itself). Example:

    https://github.com/TroyGoode/PagedList/blob/master/src/PagedList.Mvc.Example/Controllers/AutomappedListController.cs

    Links:
    https://github.com/TroyGoode/PagedList
    http://www.nuget.org/List/Packages/PagedList
    http://www.nuget.org/List/Packages/PagedList.Mvc

Comments are closed.

%d bloggers like this: