Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
В предыдущих темах было рассмотрено, как по отдельности произвести фильтрацию, сортировку и постраничный вывод. Теперь посмотрим, как можно все это объединить в одном приложении.
Для работы с проектом пусть у нас будут определены следующие модели 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; } public Company() { Users = new List<User>(); } }
Для взаимодействия с MS SQL Server через Entity Framework добавим в проект через Nuget пакет Microsoft.EntityFrameworkCore.SqlServer. А затем добавим в проект класс контекста данных UsersContext:
using Microsoft.EntityFrameworkCore; namespace FilterSortPagingApp.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(); } } }
И в классе Startup в методе ConfigureServices()
настроим использование этого контекста данных:
public void ConfigureServices(IServiceCollection services) { string connection = "Server=(localdb)\\mssqllocaldb; Database=userstoredb; Trusted_Connection=true;"; services.AddDbContext<UsersContext>(options => options.UseSqlServer(connection)); services.AddControllersWithViews(); }
Итак, нам надо произвести три операции: фильтрацию, сортировку и пагинацию. Каждая из этих операций будет представлять свой набор параметров. Для упрощения для каждого набора параметров определим собственную модель. Так, для фильтрации добавим модель FilterViewModel:
using Microsoft.AspNetCore.Mvc.Rendering; using System.Collections.Generic; namespace FilterSortPagingApp.Models { public class FilterViewModel { public FilterViewModel(List<Company> companies, int? company, string name) { // устанавливаем начальный элемент, который позволит выбрать всех companies.Insert(0, new Company { Name = "Все", Id = 0 }); Companies = new SelectList(companies, "Id", "Name", company); SelectedCompany = company; SelectedName = name; } public SelectList Companies { get; private set; } // список компаний public int? SelectedCompany { get; private set; } // выбранная компания public string SelectedName { get; private set; } // введенное имя } }
В данном случае фильтрация будет идти по компаниям и по имени пользователей, поэтому для выбранных значений определены два свойства плюс список компаний.
Для сортировки добавим перечисление SortState, которое будет описывать все возможные варианты сортировки:
public enum SortState { NameAsc, // по имени по возрастанию NameDesc, // по имени по убыванию AgeAsc, // по возрасту по возрастанию AgeDesc, // по возрасту по убыванию CompanyAsc, // по компании по возрастанию CompanyDesc // по компании по убыванию }
И затем добавим новую модель SortViewModel:
namespace FilterSortPagingApp.Models { public class SortViewModel { public SortState NameSort { get; private set; } // значение для сортировки по имени public SortState AgeSort { get; private set; } // значение для сортировки по возрасту public SortState CompanySort { get; private set; } // значение для сортировки по компании public SortState Current { get; private set; } // текущее значение сортировки public SortViewModel(SortState sortOrder) { NameSort = sortOrder == SortState.NameAsc ? SortState.NameDesc : SortState.NameAsc; AgeSort = sortOrder == SortState.AgeAsc ? SortState.AgeDesc : SortState.AgeAsc; CompanySort = sortOrder == SortState.CompanyAsc ? SortState.CompanyDesc : SortState.CompanyAsc; Current = sortOrder; } } }
Здесь для каждого свойства хранится его текущее значение SortState. Кроме того, отдельное свойство Current хранит выбранный критерий сортировки.
Для пагинации добавим новую модель PageViewModel:
using System; namespace FilterSortPagingApp.Models { public class PageViewModel { public int PageNumber { get; private set; } public int TotalPages { get; private set; } public PageViewModel(int count, int pageNumber, int pageSize) { PageNumber = pageNumber; TotalPages = (int)Math.Ceiling(count / (double)pageSize); } public bool HasPreviousPage { get { return (PageNumber > 1); } } public bool HasNextPage { get { return (PageNumber < TotalPages); } } } }
И в конце добавим общую модель IndexViewModel, которая объединит все эти модели и полученные данные:
using System.Collections.Generic; namespace FilterSortPagingApp.Models { public class IndexViewModel { public IEnumerable<User> Users { get; set; } public PageViewModel PageViewModel { get; set; } public FilterViewModel FilterViewModel { get; set; } public SortViewModel SortViewModel { get; set; } } }
После создания всех классов проект будет выглядеть следующим образом:
При фильтрации нам надо отдавать новый отфильтрованный набор пользователей. В этом плане фильтрация не зависит от параметров сортировки и пагинации. Не имеет значения на какой странице мы находимся и по какому столбцу отсортирована выборка, если мы отправляем новое значение для фильтрации.
При сортировке необходимо учитывать параметры фильтрации, то есть сортировка может идти только в отфильтрованном наборе. В то же время для сортировки не играют никакого значения параметры пагинации. И неважно, на какой странице мы находимся, если мы решили отсортировать набор по другому столбцу.
При пагинации необходимо учитывать параметры фильтрации и сортировки, то есть переход по страницам будет идти только в отсортированном и отфильтрованном наборе.Таким образом, фильтрация ни от чего не зависит. Сортировка зависит от фильтрации. А пагинация зависит от фильтрации и сортировки. Поэтому сначала надо выполнить фильтрацию, потом сортировку и в конце пагинацию. Исходя из этого, определим в контроллере HomeController следующий код:
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using FilterSortPagingApp.Models; using System.Linq; using System.Threading.Tasks; using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering; namespace FilterSortPagingApp.Controllers { public class HomeController : Controller { UsersContext db; public HomeController(UsersContext context) { this.db = context; // добавляем начальные данные if (db.Companies.Count() == 0) { 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? company, string name, int page = 1, SortState sortOrder = SortState.NameAsc) { int pageSize = 3; //фильтрация IQueryable<User> users = db.Users.Include(x=>x.Company); if (company != null && company != 0) { users = users.Where(p => p.CompanyId == company); } if (!String.IsNullOrEmpty(name)) { users = users.Where(p => p.Name.Contains(name)); } // сортировка switch (sortOrder) { case SortState.NameDesc: users = users.OrderByDescending(s => s.Name); break; case SortState.AgeAsc: users=users.OrderBy(s => s.Age); break; case SortState.AgeDesc: users = users.OrderByDescending(s => s.Age); break; case SortState.CompanyAsc: users=users.OrderBy(s => s.Company.Name); break; case SortState.CompanyDesc: users=users.OrderByDescending(s => s.Company.Name); break; default: users = users.OrderBy(s => s.Name); break; } // пагинация var count = await users.CountAsync(); var items = await users.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync(); // формируем модель представления IndexViewModel viewModel = new IndexViewModel { PageViewModel = new PageViewModel(count, page, pageSize), SortViewModel = new SortViewModel(sortOrder), FilterViewModel = new FilterViewModel(db.Companies.ToList(), company, name), Users = items }; return View(viewModel); } } }
В методе Index в качестве параметров принимаем выбранную компанию (ее id), введенное имя для поиска, номер страницы и значение сортировки. Если последние два параметра не переданы, то для них устанавливаются значения по умолчанию - первая страница и сортировка по имени по возрастанию.
Код метода Index получился довольно большим и органично разбивается на три секции: фильтрация, сортировка и пагинация. В идеале код каждой отдельной секции можно было бы выделить в отдельный класс, но в данном случае я не буду чрезмерно раздувать проект новыми классами.
И также изменим код представления Index.cshtml:
@using FilterSortPagingApp.Models @model IndexViewModel @{ ViewData["Title"] = "Home Page"; } <style> @@font-face { font-family: 'FontAwesome'; src: url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/fonts/fontawesome-webfont.woff2') format('woff2'), url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/fonts/fontawesome-webfont.woff') format('woff'), url('https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/fonts/fontawesome-webfont.ttf') format('truetype'); font-weight: normal; font-style: normal } .glyphicon { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale } .glyphicon-chevron-right:before { content: "\f054"; } .glyphicon-chevron-left:before { content: "\f053"; } </style> <h1>Список пользователей</h1> <form method="get"> <div class="form-inline"> <label>Имя: </label> <input name="name" value="@Model.FilterViewModel.SelectedName" class="form-control" /> <label>Компания: </label> <select name="company" asp-items="Model.FilterViewModel.Companies" class="form-control"></select> <input type="submit" value="Фильтр" class="btn btn-outline-dark" /> </div> </form> <table class="table"> <tr> <th> <a asp-action="Index" asp-route-sortOrder="@(Model.SortViewModel.NameSort)" asp-route-name="@(Model.FilterViewModel.SelectedName)" asp-route-company="@(Model.FilterViewModel.SelectedCompany)">Имя</a> </th> <th> <a asp-action="Index" asp-route-sortOrder="@(Model.SortViewModel.AgeSort)" asp-route-name="@(Model.FilterViewModel.SelectedName)" asp-route-company="@(Model.FilterViewModel.SelectedCompany)">Возраст</a> </th> <th> <a asp-action="Index" asp-route-sortOrder="@(Model.SortViewModel.CompanySort)" asp-route-name="@(Model.FilterViewModel.SelectedName)" asp-route-company="@(Model.FilterViewModel.SelectedCompany)">Компания</a> </th> </tr> @foreach (User u in Model.Users) { <tr><td>@u.Name</td><td>@u.Age</td><td>@u.Company.Name</td></tr> } </table> @if (Model.PageViewModel.HasPreviousPage) { <a asp-action="Index" asp-route-page="@(Model.PageViewModel.PageNumber - 1)" asp-route-name="@(Model.FilterViewModel.SelectedName)" asp-route-company="@(Model.FilterViewModel.SelectedCompany)" asp-route-sortorder="@(Model.SortViewModel.Current)" class="btn btn-outline-dark"> <i class="glyphicon glyphicon-chevron-left"></i> Назад </a> } @if (Model.PageViewModel.HasNextPage) { <a asp-action="Index" asp-route-page="@(Model.PageViewModel.PageNumber + 1)" asp-route-name="@(Model.FilterViewModel.SelectedName)" asp-route-company="@(Model.FilterViewModel.SelectedCompany)" asp-route-sortorder="@(Model.SortViewModel.Current)" class="btn btn-outline-dark"> Вперед <i class="glyphicon glyphicon-chevron-right"></i> </a> }
Так как сортировка зависит от фильтрации, то в ссылки для сортировки передаются выбранные значения при фильтрации.
И аналогично так как пагинация зависит от фильтрации и сортировки, в ссылки для пагинации передаются выбранные значения при фильтрации и сортировки.