В прошлой теме был рассмотрен постраничный вывод. Для создания ссылок применялись встроенные хелперы:
@if (Model.PageViewModel.HasPreviousPage) { <a asp-action="Index" asp-route-page="@(Model.PageViewModel.PageNumber - 1)" class="glyphicon glyphicon-chevron-left"> Назад </a> } @if (Model.PageViewModel.HasNextPage) { <a asp-action="Index" asp-route-page="@(Model.PageViewModel.PageNumber + 1)" class="glyphicon glyphicon-chevron-right"> Вперед </a> }
Но нередко для создания постраничной навигации создаются какие-то специальные панельки, где можно увидеть ссылки с номерами страниц, по которым можно перейти к нужным страницы, еще какие-то элементы. Все эти элементы нередко должным образом стилизованы.
Нередко элементы навигации - ссылки размещаются в виде ненумерованного списка. В данном случае наша задача - создать компонент навигации наподобие следующего:
<ul class="pagination"> <li><a href="/?page=1">1</a></li> <li class="active"><a>2</a></li> <li><a href="/?page=3">3</a></li> </ul>
Для стилизации применяется класс bootstrap - pagination. Перед активной ссылкой располагаются ссылки на предыдущую и следующую страницу.
Вместо того, чтобы определять все необходимые ссылки и элементы напрямую в представлении, удобнее создать специальный tag-хелпер. Поэтому возьмем проект из прошлой темы и добавим в него новую папку TagHelpers, а в эту папку добавим новый класс PageLinkTagHelper:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using MvcApp.Models; namespace MvcApp.TagHelpers { public class PageLinkTagHelper : TagHelper { IUrlHelperFactory urlHelperFactory; public PageLinkTagHelper(IUrlHelperFactory helperFactory) { urlHelperFactory = helperFactory; } [ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } = null!; public PageViewModel? PageModel { get; set; } public string PageAction { get; set; } = ""; public override void Process(TagHelperContext context, TagHelperOutput output) { if (PageModel == null) throw new Exception("PageModel is not set"); IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext); output.TagName = "div"; // набор ссылок будет представлять список ul TagBuilder tag = new TagBuilder("ul"); tag.AddCssClass("pagination"); // формируем три ссылки - на текущую, предыдущую и следующую TagBuilder currentItem = CreateTag(urlHelper, PageModel.PageNumber); // создаем ссылку на предыдущую страницу, если она есть if (PageModel.HasPreviousPage) { TagBuilder prevItem = CreateTag(urlHelper, PageModel.PageNumber - 1); tag.InnerHtml.AppendHtml(prevItem); } tag.InnerHtml.AppendHtml(currentItem); // создаем ссылку на следующую страницу, если она есть if (PageModel.HasNextPage) { TagBuilder nextItem = CreateTag(urlHelper, PageModel.PageNumber + 1); tag.InnerHtml.AppendHtml(nextItem); } output.Content.AppendHtml(tag); } TagBuilder CreateTag(IUrlHelper urlHelper, int pageNumber = 1) { TagBuilder item = new TagBuilder("li"); TagBuilder link = new TagBuilder("a"); if (pageNumber == PageModel?.PageNumber) { item.AddCssClass("active"); } else { link.Attributes["href"] = urlHelper.Action(PageAction, new { page = pageNumber }); } item.AddCssClass("page-item"); link.AddCssClass("page-link"); link.InnerHtml.Append(pageNumber.ToString()); item.InnerHtml.AppendHtml(link); return item; } } }
Фреймворк MVC предоставляет ряд сервисов, и один из них - IUrlHelperFactory, который используется для создания ссылки и который мы можем получить в конструкторе.
Всю информацию о пагинации мы получаем через свойство PageModel
. Свойство PageAction
указывает на метод контроллера, на который будет создаваться
ссылка.
Для создания ссылки используется объект IUrlHelper, а для его получения нам нужен контекст представления, в котором вызывается tag-хелпер. Получить контекст представления мы можем через внедрение зависимости через атрибуты. В частности, чтобы получить контекст представления над свойством ставится атрибут ViewContext:
[ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } = null!;
Чтобы избежать привязки к атрибутам тега, к свойству также применяется атрибут HtmlAttributeNotBound
.
В методе Process вначале получаем объект IUrlHelper для создания ссылки:
IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);
Далее создаем html-элемент ul. Затем нам надо создать максимум три ссылки, если позволяет количество страниц. Так как каждый элемент
списка ul создается одним и тем же способом, то весь механизм создания элемента списка с ссылкой вынесен в отдельный метод CreateTag()
.
Для стилизации ссылок применяются классы pagination, page-item и page-link фреймворка bootstrap, который далее подключим в представлении.
Применим этот хелпер в представлении:
@using MvcApp.Models @model IndexViewModel @*подключаем все tag-хелперы*@ @addTagHelper *, MvcApp <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <h1>Список пользователей</h1> <table class="table"> <tr><th>Имя</th><th>Возраст</th><th>Компания</th></tr> @foreach (User u in Model.Users) { <tr><td>@u.Name</td><td>@u.Age</td><td>@u.Company.Name</td></tr> } </table> <page-link page-model="Model.PageViewModel" page-action="Index"></page-link>
Так как название хелпера состоит из нескольких частей: PageLinkTagHelper, то при использовании все эти части разделяются дефисом
(суффикс TagHelper отбрасывается): page-link
. То же самое касается и свойств хелпера. Так, чтобы передать значение для свойства PageModel,
нам надо использовать атрибут page-model
.
Код контроллера остается тем же, что и в прошлой теме:
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using MvcApp.Models; namespace MvcApp.Controllers { public class HomeController : Controller { UsersContext db; public HomeController(UsersContext context) { db = context; // добавляем начальные данные if (!db.Companies.Any()) { Company oracle = new Company { Name = "Oracle" }; Company google = new Company { Name = "Google" }; Company microsoft = new Company { Name = "Microsoft" }; Company apple = new Company { Name = "Apple" }; User user1 = new User { Name = "Олег Васильев", Company = oracle, Age = 26 }; User user2 = new User { Name = "Александр Овсов", Company = oracle, Age = 24 }; User user3 = new User { Name = "Алексей Петров", Company = microsoft, Age = 25 }; User user4 = new User { Name = "Иван Иванов", Company = microsoft, Age = 26 }; User user5 = new User { Name = "Петр Андреев", Company = microsoft, Age = 23 }; User user6 = new User { Name = "Василий Иванов", Company = google, Age = 23 }; User user7 = new User { Name = "Олег Кузнецов", Company = google, Age = 25 }; User user8 = new User { Name = "Андрей Петров", Company = apple, Age = 24 }; db.Companies.AddRange(oracle, microsoft, google, apple); db.Users.AddRange(user1, user2, user3, user4, user5, user6, user7, user8); db.SaveChanges(); } } public async Task<IActionResult> Index(int page = 1) { int pageSize = 3; IQueryable<User> source = db.Users.Include(x => x.Company); var count = await source.CountAsync(); var items = await source.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync(); PageViewModel pageViewModel = new PageViewModel(count, page, pageSize); IndexViewModel viewModel = new IndexViewModel(items, pageViewModel); return View(viewModel); } } }
Итоговый проект будет выглядеть следующим образом:
И при запуске проекта мы увидим набор стилизованных ссылок, по котором сможем перемещаться по страницам: