ASP.NET MVC: Как сделать PagedList
При разработке одного из проектов (ASP.NET MVC) потребовалось реализовать постраничное отображение данных. Так как решений в интернете много, не могу не показать еще и своё решение в этой статье. А в дальнейшем планируется "завернуть" этот пример в пакет для NuGet, чтобы можно было в одно мгновение установить PagedList в проект MVC.
Постановка задачи
- Нужен интерфейс, который можно было бы использовать как Dependency Injection
- Нужно расширение для HtmlHelper, которое будет “рисовать” пейджер для переключения страниц
- Также потребуется расширение, чтобы использовать “страничность” по принципу fluid.
Интерфейс IPagedList
На самом деле интерфейс на столько прост, а названия его свойств на столько говорят о себе, что я не стану описывать данный код, просто приведу его целиком:
public interface IPagedList
{
int TotalPages { get; }
int TotalCount { get; }
int PageIndex { get; }
int PageSize { get; }
bool IsPreviousPage { get; }
bool IsNextPage { get; }
}
В силу небольшой сложности интерфейса, реализация его также проста. Я сделал два конструктора, чтобы можно было “подкидывать” в него разные типы списков.
public PagedList(IQueryable<T> source, int index, int pageSize)
{
/* ... */
}
и второй:
public PagedList(List<T> source, int index, int pageSize)
{
/* ... */
}
Теперь приступим к реализации расширения для HtmlHelper. Я хочу сделать так, чтобы можно было пейджер разместить на странице вот таким образом:
@Html.PagerForPagedList(Model.PageIndex, Model, "pager")
Сигнатуру конструктора опишу ниже.
Расширение HtmlHelper
Нехитрыми манипуляциями с TagBuilder и MvcHtmlString моё расширение выдает на страницу такую разметку:
<div class="pager">
<ul>
<li class="pager highlighted">
<span>
<a href="/Home/index/1">
<span>1</span>
</a>
</span>
</li>
<li>
<span>
<a href="/Home/index/2">
<span>2</span>
</a>
</span>
</li>
</ul>
</div>
Обращу Ваше внимание на то, что текущая (выбранная) страница подсвечивается (см. строку 3), а сам список обёрнут в div (см. строку 1). Приводить код CSS-файла пока не буду, потому что надеюсь, что Вы и сами сможете кучку <li> разместить горизонтально при помощи CSS. Прошу у Вас только одного совета. Дело в том, что хотелось бы от Вас услышать “нормальная” ли структура, подойдет ли она для большинства CSS, которыми пользуются люди в мире? Просто уже очень много CSS, которые так или иначе преобразуют тэги UL и LI в меню, списки и т.д. Вот и хотелось бы “подогнать” генерируемый код под общепринятые стандарты.
Расширение для PagedList
Приведу текст кода полностью, потому что он совсем даже небольшой:
public static class PagedListExtentions
{
public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index, int pageSize)
{
return new PagedList<T>(source, index, pageSize);
}
public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index)
{
return new PagedList<T>(source, index, 10);
}
}
Пример использования PagedList
Хочу чтобы на главной странице Home-контролера был список чего-нибудь. О! Я же уже делал один пакет NuGet с данными для демонстраций. Его-то я и буду использовать.
Установил пакет:
Install-Package SampleData
Сначала подключу namespace библиотеки примеров.
using SampleData;
Теперь код Home-контроллера:
public class HomeController : Controller
{
public ActionResult Index(int? id)
{
int currentPage = id ?? 1;
ViewBag.Message = "Welcome to ASP.NET MVC!";
List<Person> list = SampleData.People.GetPeople();
return View(list.AsQueryable().ToPagedList<Person>(currentPage - 1));
}
public ActionResult About()
{
return View();
}
}
В строке 7 создаем список и наполняем его данными. А в следующей строке возвращаем данные уже разбитые на страницы подставляя номер выбранной страницы полученной как параметр в конструкторе метода (строка 3). Настройку маршрутов менять не пришлось, оставил как и было, потому что есть параметр, который может принимать значение null.
Дабы избежать многочисленных слов в описании представления (View) просто покажу весь текст разметки:
@model MvcApplication2.Engine.PagedList<SampleData.Person>
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>
Total: @Model.TotalCount
<br />
Page size: @Model.PageSize</p>
<ul>
@foreach (SampleData.Person pers in Model)
{<li>
@Html.DisplayFor(x => pers.Name)
@Html.DisplayFor(x => pers.Weight)
@Html.DisplayFor(x => pers.Gender)
@Html.DisplayFor(x => pers.Age)</li>
}
</ul>
<div>
@Html.PagerForPagedList(Model.PageIndex, Model, "pager")
</div>
Отмечу следующие строки:
1-ая - определяем модель в представлении
8-ая – отображаем общее количество объектов
10-ая - отображаем размер страницы
с 12 по 28 – рисуем объект
21-ая – рисуем пейджер
Первым параметром в PagerForPagedList идет номер выбранной страницы, далее экземпляр PagedList и последний параметр это название CSS-стиля для пейджера:
.pager
{
width: 100%;
margin: 0px;
display: inline-block;
padding: 0px;
}
.pager ul
{
list-style: none;
margin: 0px;
padding: 0px;
}
.pager ul li
{
float: left;
min-width:30px;
text-align:center;
}
.pager ul li span
{
display: block;
margin: 0px 1px;
padding: 4px;
text-decoration:none;
border: 1px solid #ccc;
}
.pager ul li a
{
display: block;
margin: 0px 1px;
padding: 4px;
text-decoration:none;
border: 1px solid #ccc;
}
.highlighted
{
background-color: #ccc;
color: #000;
}
Эти стили обнговлены для версии 0.5.0
Вот такой вид имеет приложение при старте.

Заключение
Как по Вашему мнению, следует ли данный пример попытаться завернуть в NuGet-пакет, чтобы возможно было в одно мгновение подключит к проекту MVC3 пейджинг сущностей на страницах сайта? Если да, то какие будут замечания и пожелания?
P.S.: Да прибудет сила с Вами!
Сделал-таки NuGet-пакет
Обновление 19.06.2011 года:
Сделал пакет. Можно установить его написав команду в консоли управления пакетами:
PM> Install-Package PagedListExt
или другим способом.
После создания проекта MVC3 и установки пакета SampleData и PagedListExt (описанный в этой статье) достаточно добавить код в HomeController:
public class HomeController : Controller
{
public ActionResult Index(int? id)
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
int index = id ?? 1;
IQueryable<Person> query = SampleData.People.GetPeople().AsQueryable();
return View(query.ToPagedList(index));
}
public ActionResult About()
{
return View();
}
}
В _Layout.cshtml надо добавить строку с Css:
<link href="@Url.Content("~/Content/pager.css")" rel="stylesheet" type="text/css" />
А вот так выглядит Index.cshtml:
@model PagedList<SampleData.Person>
@{
ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<p>
@Html.DisplayFor(x => @Model)</p>
<p>
@Html.PagerForPagedList(@Model.PageIndex, @Model, "pager")</p>
и всё заработает. Отмечу только внешний вид отображаемых данных при этой разметки оставляет желать лучшего. Просто для простоты не стал “разукрашивать” представление класса Person.
Пробуйте, пишите, критикуйте, советуйте…
Обновление 11.07.2011 года:
В обновленной версии 0.3.2 расширены меторы дополнительным параметром routeValues и появились новые методы использующие IEnumerable<T> для построения PagedList.
Обновление 0.3.4. от 07.10.2011:
В новой версии добавлено:

Обновление 0.4.3 от 18.01.2012:
В PrevNextForPagedList добавлен параметр RouteValues.
Обновление 0.5.0 от 22.03.2012:
В новой версии многое изменилось:
- теперь можно использовать стили от jQueryUI если не задавать параметр pagerCss.
- файл pager.css удален из проекта, вы можете теперь создавать свои стили. Пример смотрите выше.
- добавлена возможность задавать количество сегментов отображаемых страниц в режиме PagerForPagedList
- исправлена ошибка при использовании режима PrevNextForPagedList: не скрывалась кнопка "следующая" при достижении конца коллекции.
Обновление 0.7.0 от 12.10.2013:
Пакет адаптирован для использования совместно с Bootstrap 3.0. Для того чтобы использовать возможность разметки bootstrap, установите свойство pagerCss равным “pagination”.
Обновление 0.7.5 от 11.02.2014:
Добавил несколько перегрузок методов.
Обновление 0.8.0 от 07.11.2014:
Добавил специальный метод непосредственно для Bootstrap. Теперь можно задавать шаблон URL для генерации пейджера. Например:
string department = ViewBag.DepartmentUrlName;
string catalog = ViewBag.CatalogUrlName;
tring subcatalog = ViewBag.SubcatalogUrlName;
var url = string.Format("/items/{0}/{1}/{2}/{3}", department, catalog, subcatalog, "{PageIndex}");
@Html.PagerForPagedListBootstrap(Model, url)
В строке 5 новый метод MvcHelper’а. который принимает как параметр IPagedList (в примере это переменная “Model”) и второй TemplateUrl для построения pagination через bootstrap.
Ссылки
ASP.NET MVC3: Как сделать PagedList на AJAX
PagedListLite.MVC