Пагинация или постраничный вывод позволяет разбить набор объектов на отдельные страницы. Подобный механизм особенно удобен, если данных очень много - десятки, сотни и даже тысячи объектов. Рассмотрим пару способов, как добавить в проект постраничную навигацию.
Для работы возьмем модели User и Company:
public class User { public int Id { get; set; } public string? Name { get; set; } public int Age { get; set; } public int? CompanyId { get; set; } public Company? Company { get; set; } } public class Company { public int Id { get; set; } public string? Name { get; set; } public List<User> Users { get; set; } = new(); }
Для взаимодействия с MS SQL Server через Entity Framework добавим в проект через Nuget пакет Microsoft.EntityFrameworkCore.SqlServer. А затем добавим в проект класс контекста данных UsersContext:
using Microsoft.EntityFrameworkCore; namespace MvcApp.Models { public class UsersContext : DbContext { public DbSet<User> Users { get; set; } public DbSet<Company> Companies { get; set; } public UsersContext(DbContextOptions<UsersContext> options) : base(options) { Database.EnsureCreated(); } } }
И в файле Program.cs настроим использование этого контекста данных:
using Microsoft.EntityFrameworkCore; using MvcApp.Models; var builder = WebApplication.CreateBuilder(args); string connection = "Server = (localdb)\\mssqllocaldb;Database = userstoredb;Trusted_Connection=true"; builder.Services.AddDbContext<UsersContext>(options => options.UseSqlServer(connection)); builder.Services.AddControllersWithViews(); var app = builder.Build(); app.MapDefaultControllerRoute(); app.Run();
Далее в проект новый класс PageViewModel, который будет содержать всю информацию о пагинации:
namespace MvcApp.Models { public class PageViewModel { public int PageNumber { get;} public int TotalPages { get;} public bool HasPreviousPage => PageNumber > 1; public bool HasNextPage => PageNumber < TotalPages; public PageViewModel(int count, int pageNumber, int pageSize) { PageNumber = pageNumber; TotalPages = (int)Math.Ceiling(count / (double)pageSize); } } }
Данный класс хранит номер текущей страницы в свойстве PageNumber, общее количество страниц в свойстве TotalPages, а также определяет свойства HasPreviousPage и HasNextPage, с помощью которых можно узнать, есть ли до и после текущей страницы еще какие-нибудь страницы.
Определим также в папке Models класс IndexViewModel, который будет инкапсулировать информацию о пагинации и полученных пользователей:
namespace MvcApp.Models { public class IndexViewModel { public IEnumerable<User> Users { get;} public PageViewModel PageViewModel { get;} public IndexViewModel(IEnumerable<User> users, PageViewModel viewModel) { Users = users; PageViewModel = viewModel; } } }
В итоге весь проект будет выглядеть следующим образом:
Используем все эти классы в контроллере HomeController:
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); } } }
Сама пагинация производится в методе Index()
. В этот метод передается номер страницы. Если номер страницы явным образом не передан, то считается, что
мы получаем данные для первой страницы.
Далее получаем общее количество элементов в переменную count:
var count = await source.CountAsync();
Затем собственно выполняется пагинация - с помощью метода Skip()
пропускаем нужное количество элементов, чтобы достичь нужной страницы.
И с помощью метода Take()
выбираем нужную порцию элементов, которая равна размеру страницы.
И далее возвращается объект IndexViewModel, который хранит всю информацию о пагинации и который передается в представление.
И в конце для метода Index контроллера HomeController определим следующее представление Index.cshtml:
@using MvcApp.Models @model IndexViewModel @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <style> .glyphicon { display: inline-block; padding:0 5px;} .glyphicon-chevron-right:after { content: "\00BB"; } .glyphicon-chevron-left:before { content: "\00AB"; } </style> <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> <p> @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> } </p>
После вывода списка в таблице формируем ссылки для перехода назад и вперед: