Tag-хелпер для постраничной навигации

Последнее обновление: 12.04.2022

В прошлой теме был рассмотрен постраничный вывод. Для создания ссылок применялись встроенные хелперы:

@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);
        }
    }
}

Итоговый проект будет выглядеть следующим образом:

Pagination in ASP.NET Core MVC and C#

И при запуске проекта мы увидим набор стилизованных ссылок, по котором сможем перемещаться по страницам:

Постраничная навигация в ASP.NET Core MVC и C#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850