ASP.NET MVC: Как сделать PagedList

Сайтостроение | создано: 17.06.2011 | опубликовано: 18.06.2011 | обновлено: 13.01.2024 | просмотров: 36816 | всего комментариев: 23

При разработке одного из проектов (ASP.NET MVC) потребовалось реализовать постраничное отображение данных. Так как решений в интернете много, не могу не показать еще и своё решение в этой статье. А в дальнейшем планируется "завернуть" этот пример в пакет для NuGet, чтобы можно было в одно мгновение установить PagedList в проект MVC.

Постановка задачи

  1. Нужен интерфейс, который можно было бы использовать как Dependency Injection
  2. Нужно расширение для HtmlHelper, которое будет “рисовать” пейджер для переключения страниц
  3. Также потребуется расширение, чтобы использовать “страничность” по принципу 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

Вот такой вид имеет приложение при старте.

image

Заключение

Как по Вашему мнению, следует ли данный пример попытаться завернуть в 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:

В новой версии добавлено:

prev_next_pager

Обновление 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

Комментарии к статье (23)

12.01.2012 21:53:00 Андрей ASP.NET MVC: Как сделать PagedList

Большое спасибо за труд. Может Вы подскажете, как сделать Paging с вашим примером, но добавить в URl другие параметры (кроме страницы): categoryid, department и т.п. (domen.ru/odezhda/page-2/ вот так нужно)
Заранее, спаибо.

Вы можете передать любые параметры и любое количество через routedvalues.

Например:
 

@Html.PagerForPagedList(@Model.PageIndex, @Model.SelectedItems, "pager", "index", new {
  typeid = @Model.TypeId,
  dealtypeid = @Model.DealTypeId,
  cityid = @Model.CityId,
  districtid = @Model.DistrictId,
  countryid = @Model.CountryId,
  roomid = @Model.RoomId,
  roomtypeid = @Model.RoomTypeId,
  pricefrom = @Model.PriceFrom,
  priceto = @Model.PriceTo
})
12.03.2012 21:10:00 Андрей ASP.NET MVC: Как сделать PagedList
Огромное спасибо Вам!!!
Сижу разбираюсь. Думаю, все получится!!
Еще раз спасибо.
07.04.2012 11:45:00 Varun Maggo ASP.NET MVC: Как сделать PagedList
Long live russians :)

Best Regards

Во первых ОГРОМНОЕ СПАСИБО!!

Но есть некоторые проблемы :(
версия паркета 0,5,1

На странице пишу:

@Html.PrevNextForPagedList(Model.PageIndex, Model, "Person", "List", "Назад", "Вперед", true, true, "pager")

на странице строится неправильная ссылка:
<a  href="/Person/Person/2?Length=4" >

18.05.2012 14:58:25 Нурлан ASP.NET MVC: Как сделать PagedList

Ничего не понятно, каша на каше, хотя бы видео сделайте.

Видеоурок намного лучше всей писанины !!!

Чтоб не было "Length=4" попробуйте так

@Html.PrevNextForPagedList(Model.PageIndex, Model, "Person", "List", "Назад", "Вперед", true, true, "pager", false)

I am using the plugin with a paging server side, so I, for example, a collection of 1000 elements and visualize 10 at a time. Using SetTotalCount are properly render the indexes page present but when I click on the third page, I see selected the first page, how can I fix this?

 

To antonio...

Method "SetTotalCount" was added for using with AutoMapper. You do not have to use this method. Please, use plugin like this:

public ViewResult Index(int? id) {
     var model = exhibitRepository
     .AllIncluding(exhibit => exhibit.Hall, exhibit => exhibit.Tags)
     .ToViewModels();
      return View(model.ToPagedList(id ?? 1));
}

thanks for te reply but I don't understand.

my repository I will only return 10 items at a time, or those who want to appear on each page, but the total elements are 1000, how can I do?

To antonio...

Would you like to download a demo project and see how it works?

Thanks for the reply.

I'm sorry, the problem is probably my English.

The plugin works correctly if I use it as an example but I need to use it in slightly different ways.

I need to make a paging SERVER side, so I use the collection as a parameter to the PagerForPagedList has ALWAYS up to 10 elements, although there are actually 1000, why use SetTotalCount (), which seems to work well for the generation index page but when you click on the number 3, url = http://localhost:41343/MyPage/Action/3, turns ALWAYS number 1

looks like cache or wrong parameter you are using for generate pager's list... would you can send me a  demo project... or simple version of this trouble?

Новая версия пакета вышла.

To Antonio... Fixed bug drop (in some situations) current page index (thanks Antonio). Method SetTotal was removed. The library's size decreased.

Hi,

the bug is still there, I have prepared the sample code, give me your email and I will send, thanks

07.04.2013 23:01:55 Марина ASP.NET MVC: Как сделать PagedList

Попыталась использовать ваш хелпер для приложения, где список выводится на главной странице, но не в файле Index.cshtml, а в частичном представлении, 3-ем или 4-ом по вложенности относительно Index... В адрес значение нажатой кнопочки страницы выводится, а вот в процедуру обработки ни Index, ни того контроллера, что обрабатывает непосредственно непосредственно список не попадает. Возможно я совсем не понимаю, как работает хелпер...

Марина

Очень трудно вот так вот, "с ходу" не то чтобы сказать в чем проблема, но и понять о чем идет речь. Никак не обойтись без примера кода. Могу дать вам своё мыло, отправите проект к с проблемой?

08.04.2013 14:50:40 Марина ASP.NET MVC: Как сделать PagedList

Понятно, что сказать трудно. Тестовый сайт находится по адресу: http://knowit-all-order.servis-technicalsoft.ru/

Нужно зайти в раздел Производители и там выбрать Издательства

Это то, что в результате использования PagedList на сегодня получилосилось. Нет форматирования списка и нет перехода на страницу.

Частичное представление _sh_Enterprise.cshtml содержит код:

[ code removed ]

Код функции-обработчика

[ code removed ]

Если вы датите e-mail я могу и полный код кинуть

Марина, я отправил вам мыло... Жду проект.

macros 21.04.2012

Во первых ОГРОМНОЕ СПАСИБО!!
Но есть некоторые проблемы :(
версия паркета 0,5,1
На странице пишу:
@Html.PrevNextForPagedList(Model.PageIndex, Model, "Person", "List", "Назад", "Вперед", true, true, "pager")
на странице строится неправильная ссылка:
<a  href="/Person/Person/2?Length=4" >

zeone 16.09.2012

Чтоб небыло  Length=4" попробуйте так

@Html.PrevNextForPagedList(Model.PageIndex, Model, "Person", "List", "Назад", "Вперед", true, true, "pager", false)

Проблема осталась как убрать ?Length=4 ?????

Чтоб небыло  Length=4" попробуйте так

@Html.PrevNextForPagedList(Model.PageIndex, Model, "index", null, "Назад", "Вперед", true, true, "pager")

Hi, Pager looks good, but I can't get it to output anything.

I have a controller (I have simplified query):

public ActionResult Index(JobSearch model)

        {
            var entities = from m in db.Jobs
                             select m;

            var results = entities.Where(c => c.Status.Equals(model.Status))
                   .OrderBy(o => o.JobId);

            model.SearchResults = results.ToPagedList(model.Page, model.RecordsPerPage);
            return View(model);
        }

Model:

public class JobSearch
    {
        private int _Page = 1;
        public int Page
        {
            get { return _Page; }
            set { _Page = value; }
        }

        private int _RecordsPerPage = 25;
        [Display(Name = "Records per Page")]
        public int RecordsPerPage
        {
            get { return _RecordsPerPage; }
            set { _RecordsPerPage = value; }
        }

        [Display(Name = "Job Status")]
        public int Status { get; set; }

        public IPagedList SearchResults { get; set; }

    }

And view

@model FH12052701.Models.JobSearch

...

@Html.PagerForPagedList(Model.Page, Model.SearchResults, "index")

The PagerForPagedList returns nothing, even when Model.SearchResults contains records.

What am I doing wrong?

Chuck

Hi, Chuck

Everything in your code looks good, and I advise you try another signature, for example, like this:

@Html.PrevNextForPagedList(Model.PageIndex, Model, "index", null, "back", "forward", true, true, "pager")

If this does not help you, I need to see a project to resolve your problem.