Часть 3. Razor Страницы с EF Core ASP.NET Core — сортировка, фильтрация, разбиение по страницам
Авторы: Том Дайкстра (Tom Dykstra), Джереми Ликнесс (Jeremy Likness) и Йон П. Смит (Jon P Smith)
Веб-приложение Contoso University демонстрирует создание Razor веб-приложений Pages с помощью EF Core Visual Studio. Сведения о серии руководств см. в первом руководстве серии.
При возникновении проблем, которые вам не удается устранить, скачайте готовое приложение и сравните его код с тем, который вы создали в процессе работы с этим руководством.
В этом учебнике вы добавите на страницу учащихся функции сортировки, фильтрации и разбиения на страницы.
На следующем рисунке показана готовая страница. Заголовки столбцов являются ссылками, щелкнув которые, можно отсортировать столбец. Щелкайте заголовок столбца для переключения между сортировкой по возрастанию и убыванию.
Добавление сортировки
Замените код в Pages/Students/Index.cshtml.cs
следующем коде, чтобы добавить сортировку.
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public IList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder)
{
// using System;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
}
}
Предыдущий код:
- Требует добавления
using System;
. - добавляет свойства, которые будут содержать параметры сортировки;
- изменяет имя свойства
Student
наStudents
; - заменяет код в методе
OnGetAsync
.
Метод OnGetAsync
принимает параметр sortOrder
из строки запроса в URL-адресе. URL-адрес и строка запроса формируются вспомогательной функцией тегов привязки.
Параметр sortOrder
имеет значение Name
или Date
. После параметра sortOrder
может стоять _desc
, чтобы указать порядок по убыванию. По умолчанию задан порядок сортировки по возрастанию.
При запросе страницы "Index" по ссылке Students строка запроса отсутствует. Учащиеся отображаются по фамилии в порядке возрастания. В операторе default
сортировка по фамилии в порядке возрастания используется по умолчанию (switch
). Когда пользователь щелкает ссылку заголовка столбца, в строке запроса указывается соответствующее значение sortOrder
.
Для формирования гиперссылок в заголовках столбцов страница Razor использует NameSort
и DateSort
с соответствующими значениями строки запроса:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
В коде используется условный оператор C# ?:. Оператор ?:
является тернарным (принимает три операнда). Первая строка указывает, что, когда sortOrder
равен null или пуст, NameSort
имеет значение name_desc
. Если sortOrder
не является NULL или пустым, для NameSort
задается пустая строка.
Следующие два оператора устанавливают гиперссылки в заголовках столбцов на странице следующим образом:
Текущий порядок сортировки | Гиперссылка "Last Name" (Фамилия) | Гиперссылка "Date" (Дата) |
---|---|---|
"Last Name" (Фамилия) по возрастанию | по убыванию | ascending |
"Last Name" (Фамилия) по убыванию | ascending | ascending |
"Date" (Дата) по возрастанию | ascending | по убыванию |
"Date" (Дата) по убыванию | ascending | ascending |
Для указания столбца, по которому выполняется сортировка, этот метод использует LINQ to Entities. Код инициализирует IQueryable<Student>
до оператора switch и изменяет его в этом операторе:
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
При создании или изменении IQueryable
запрос в базу данных не отправляется. Запрос не выполнится, пока объект IQueryable
не будет преобразован в коллекцию. IQueryable
преобразуются в коллекцию путем вызова метода, такого как ToListAsync
. Таким образом, код IQueryable
создает одиночный запрос, который не выполняется до следующего оператора:
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync
можно расширить на случай большого числа сортируемых столбцов. Сведения об альтернативном способе программирования этой функциональности см. в статье Использование динамических запросов LINQ для упрощения кода в версии этой серии учебников для MVC.
Добавление гиперссылок для заголовков столбцов на странице индексов учащихся
Замените код в Students/Index.cshtml
следующим: Изменения выделены.
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Предыдущий код:
- Добавляет гиперссылки в заголовки столбцов
LastName
иEnrollmentDate
. - Использует эти сведения в
NameSort
иDateSort
для настройки гиперссылок с использованием текущих значений порядка сортировки. - Изменяет заголовок страницы с Index (Индекс) на Students (Учащиеся).
- Изменяет
Model.Student
наModel.Students
.
Чтобы проверить работу сортировки, сделайте следующее:
- Запустите приложение и откройте вкладку Students (Учащиеся).
- Щелкните заголовки столбцов.
Добавление фильтрации
Для добавления фильтрации на страницу индекса учащихся:
- На страницу Razor добавляется текстовое поле и кнопка отправки. Текстовое поле предоставляет строку поиска для имени или фамилии.
- Страничная модель обновляется для использования значения из текстового поля.
Обновление метода OnGetAsync
Замените код в Students/Index.cshtml.cs
следующем коде, чтобы добавить фильтрацию:
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public IList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder, string searchString)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
CurrentFilter = searchString;
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
}
}
Предыдущий код:
- Добавляет параметр
searchString
в методOnGetAsync
и сохраняет значение параметра в свойствеCurrentFilter
. Значение строки поиска получается из текстового поля, добавляемого в следующем разделе. - Добавляет в инструкцию LINQ предложение
Where
. Это предложениеWhere
отбирает только учащихся, чье имя или фамилия содержат строку поиска. Оператор LINQ выполняется, только если задано значение для поиска.
IQueryable vs. IEnumerable
Код вызывает метод Where объекта IQueryable
, при этом фильтр обрабатывается на сервере. В некоторых случаях приложение может вызывать метод Where
как метод расширения для коллекции в памяти. Например, предположим _context.Students
, что изменения из EF CoreDbSet
метода репозитория, возвращающего коллекцию IEnumerable
. Обычно результат остается прежним, но в некоторых случаях он может отличаться.
Например, реализация Contains
в .NET Framework по умолчанию выполняет сравнение с учетом регистра. В SQL Server Contains
учет регистра определяется параметрами сортировки у экземпляра SQL Server. По умолчанию SQL Server не учитывает регистр. В SQLite по умолчанию регистр учитывается. Можно вызвать ToUpper
, чтобы явно велеть тесту не учитывать регистр.
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
Приведенный выше код гарантирует, что фильтр не учитывает регистр, даже если метод Where
вызывается для IEnumerable
или выполняется в SQLite.
Когда Contains
вызывается для коллекции IEnumerable
, используется реализация .NET Core. Когда Contains
вызывается для объекта IQueryable
, используется реализация базы данных.
Вызов Contains
для IQueryable
обычно предпочтительнее по соображениям производительности. При использовании IQueryable
фильтрация выполняется сервером базы данных. Если сначала создается IEnumerable
, все строки должны возвращаться с сервера базы данных.
Вызов ToUpper
снижает производительность. Код ToUpper
добавляет функцию в предложение WHERE TSQL-оператора SELECT. Добавленная функция не позволяет оптимизатору использовать индекс. Учитывая, что SQL устанавливается без учета регистра, рекомендуется не использовать вызов ToUpper
, когда он не требуется.
Дополнительные сведения см. в статье Использование запроса без учета регистра с поставщиком SQLite.
Обновление страницы Razor
Замените код, Pages/Students/Index.cshtml
чтобы добавить кнопку поиска .
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Для добавления кнопки и поля поиска предыдущий код использует вспомогательную функцию тегов<form>
. По умолчанию вспомогательная функция тегов <form>
отправляет данные формы с помощью POST. При этом параметры передаются в тексте сообщения HTTP, а не в URL-адресе. При использовании HTTP GET данные формы передаются в виде строк запроса в URL-адресе. Передача данных со строками запроса позволяет пользователям добавлять URL-адрес в закладки. Руководства консорциума W3C рекомендуют использовать GET, когда действие не приводит к обновлению.
Проверьте работу приложения:
Выберите вкладку Students (Учащиеся) и введите строку поиска. При использовании SQLite фильтр не учитывает регистр, только если вы реализовали необязательный код
ToUpper
, приведенный ранее.Нажмите Поиск.
Обратите внимание, что URL-адрес содержит строку поиска. Например:
https://localhost:5001/Students?SearchString=an
Если страница добавлена в закладки, закладка содержит URL-адрес страницы и строку запроса SearchString
. Формирование строки запроса обеспечивает method="get"
в теге form
.
Сейчас при выборе ссылки сортировки заголовка столбца теряется значение фильтра в поле Search (Поиск). Потерянное значение фильтра исправляется в следующем разделе.
Добавление разбиения по страницам
В этом разделе создается класс PaginatedList
для поддержки разбиения на страницы. Класс PaginatedList
использует операторы Skip
и Take
для фильтрации данных на сервере вместо того, чтобы извлекать все строки таблицы. На следующем рисунке показаны кнопки перелистывания.
Создание класса PaginatedList
В папке проекта создайте файл PaginatedList.cs
со следующим кодом:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
В предыдущем коде метод CreateAsync
принимает размер и номер страницы и вызывает соответствующие методы Skip
и Take
объекта IQueryable
. Метод ToListAsync
объекта IQueryable
при вызове возвращает список, содержащий только запрошенную страницу. Для включения и отключения кнопок перелистывания страниц Previous (Назад) и Next (Далее) используются свойства HasPreviousPage
и HasNextPage
.
Метод CreateAsync
используется для создания PaginatedList<T>
. Конструктор не позволяет создать объект PaginatedList<T>
, так как конструкторы не могут выполнять асинхронный код.
Добавление размера страницы в конфигурацию
Добавьте PageSize
в файл appsettings.json
конфигурации:
{
"PageSize": 3,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Добавление разбиения по страницам в IndexModel
Замените код, Students/Index.cshtml.cs
чтобы добавить разбиение по страницам.
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
private readonly IConfiguration Configuration;
public IndexModel(SchoolContext context, IConfiguration configuration)
{
_context = context;
Configuration = configuration;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public PaginatedList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
CurrentFilter = searchString;
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
var pageSize = Configuration.GetValue("PageSize", 4);
Students = await PaginatedList<Student>.CreateAsync(
studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
}
}
Предыдущий код:
- изменяет тип свойства
Students
сIList<Student>
наPaginatedList<Student>
; - добавляет индекс страницы, текущий порядок
sortOrder
и фильтрcurrentFilter
в сигнатуру методаOnGetAsync
; - сохраняет порядок сортировки в свойстве
CurrentSort
; - сбрасывает индекс страницы в значение 1 при получении новой строки поиска;
- использует класс
PaginatedList
для получения сущностей Student. - Задает для
pageSize
значение 3 из файла конфигурации или 4, если настройка завершается сбоем.
Все параметры, получаемые методом OnGetAsync
, равны NULL, когда:
- Страница вызывается по ссылке Students (Учащиеся).
- Пользователь не открывал ссылку перелистывания или сортировки.
При выборе ссылки перелистывания переменная индекса страницы содержит номер страницы для отображения.
Свойство CurrentSort
предоставляет странице Razor текущий порядок сортировки. Текущий порядок сортировки нужно включить в ссылки перелистывания, чтобы сохранить его при смене страницы.
Свойство CurrentFilter
предоставляет странице Razor текущую строку фильтра. Значение CurrentFilter
:
- Нужно включить в ссылки для перелистывания, чтобы сохранить параметры фильтра при смене страницы.
- Нужно восстановить в текстовом поле после обновления страницы.
Если строка поиска изменяется во время перелистывания, номер страницы сбрасывается в значение 1. Номер страницы должен быть сброшен на 1, так как с новым фильтром может измениться состав отображаемых данных. Если введено значение для поиска и нажата кнопка Submit (Отправить):
- Строка поиска изменяется.
- Значение параметра
searchString
отличается от null.
Метод PaginatedList.CreateAsync
преобразует результат запроса учащихся в отдельную страницу коллекции, поддерживающую разбиение на страницы. Эта страница с учащимися передается на страницу Razor.
Два вопросительных знака после pageIndex
в вызове PaginatedList.CreateAsync
являются оператором объединения с NULL. Оператор объединения с null определяет значение по умолчанию для типа, допускающего значение null. Выражение pageIndex ?? 1
возвращает значение свойства pageIndex
, если оно есть. В противном случае возвращается значение 1.
Добавление ссылок для разбиения по страницам
Замените код в Students/Index.cshtml
следующим: Изменения выделены:
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@{
var prevDisabled = !Model.Students.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Students.HasNextPage ? "disabled" : "";
}
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>
Ссылки в заголовках столбцов передают текущую строку поиска в метод OnGetAsync
с помощью строки запроса:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
Кнопки перелистывания отображаются вспомогательными функциями тегов:
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>
Запустите приложение и перейдите на страницу учащихся.
- Чтобы убедиться, что разбиение на страницы работает, нажимайте кнопки перелистывания при различном порядке сортировки.
- Чтобы убедиться, что разбиение на страницы работает корректно вместе с сортировкой и фильтрацией, введите строку поиска и попробуйте перелистнуть страницу.
Группировка
В этом разделе показано, как создать страницу общих сведений About
, на которой показано количество учащихся, зарегистрированных на каждую дату. Это изменение использует группирование и включает следующие шаги:
- Создайте модель представления для данных, используемых страницей
About
. - Обновите страницу
About
для использования модели представления.
Создание модели представления
Создайте папку Models/SchoolViewModels.
Создайте SchoolViewModels/EnrollmentDateGroup.cs
, используя следующий код:
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
public int StudentCount { get; set; }
}
}
Создание Razor страницы
Создайте файл Pages/About.cshtml
со следующим кодом:
@page
@model ContosoUniversity.Pages.AboutModel
@{
ViewData["Title"] = "Student Body Statistics";
}
<h2>Student Body Statistics</h2>
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
Создание модели страницы
Pages/About.cshtml.cs
Обновите файл со следующим кодом:
using ContosoUniversity.Models.SchoolViewModels;
using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;
namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;
public AboutModel(SchoolContext context)
{
_context = context;
}
public IList<EnrollmentDateGroup> Students { get; set; }
public async Task OnGetAsync()
{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
Students = await data.AsNoTracking().ToListAsync();
}
}
}
Запрос LINQ группирует записи из таблицы студентов по дате зачисления, вычисляет число записей в каждой группе и сохраняет результаты в коллекцию объектов моделей представления EnrollmentDateGroup
.
Запустите приложение и перейдите на страницу "About" (О программе). Количество зачисленных студентов по дням отображается в таблице.
Следующие шаги
В следующем руководстве приложение использует миграции для обновления модели данных.
Это руководство описывает добавление функций сортировки, фильтрации, группировки и разбиения на страницы.
На следующем рисунке показана готовая страница. Заголовки столбцов являются ссылками, щелкнув которые, можно отсортировать столбец. Многократно щелкая заголовок столбца, можно переключаться между сортировкой по возрастанию и убыванию.
При возникновении проблем, которые вам не удается устранить, скачайте готовое приложение.
Добавление сортировки на страницу индекса
Добавьте строки в файл Students/Index.cshtml.cs
PageModel
для хранения параметров сортировки:
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
Измените Students/Index.cshtml.cs
OnGetAsync
, используя следующий код:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentIQ = from s in _context.Student
select s;
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
Предыдущий код принимает параметр sortOrder
из строки запроса в URL-адресе. URL-адрес (включая строку запроса) формируется вспомогательной функцией тегов привязки
Параметр sortOrder
имеет значение "Name" или "Date". После параметра sortOrder
может стоять "_desc", чтобы указать порядок по убыванию. По умолчанию задан порядок сортировки по возрастанию.
При запросе страницы "Index" по ссылке Students строка запроса отсутствует. Учащиеся отображаются по фамилии в порядке возрастания. В операторе switch
сортировка по фамилии в порядке возрастания используется по умолчанию. Когда пользователь щелкает ссылку заголовка столбца, в строке запроса указывается соответствующее значение sortOrder
.
Для формирования гиперссылок в заголовках столбцов страница Razor использует NameSort
и DateSort
с соответствующими значениями строки запроса:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentIQ = from s in _context.Student
select s;
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
Следующий код содержит условный оператор ?: C#:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
Первая строка указывает, что когда sortOrder
равен null или пуст, NameSort
имеет значение "name_desc". Если sortOrder
не является NULL или пустым, для NameSort
задается пустая строка.
?: operator
также называется тернарным оператором.
Следующие два оператора устанавливают гиперссылки в заголовках столбцов на странице следующим образом:
Текущий порядок сортировки | Гиперссылка "Last Name" (Фамилия) | Гиперссылка "Date" (Дата) |
---|---|---|
"Last Name" (Фамилия) по возрастанию | по убыванию | ascending |
"Last Name" (Фамилия) по убыванию | ascending | ascending |
"Date" (Дата) по возрастанию | ascending | по убыванию |
"Date" (Дата) по убыванию | ascending | ascending |
Для указания столбца, по которому выполняется сортировка, этот метод использует LINQ to Entities. Код инициализирует IQueryable<Student>
до оператора switch и изменяет его в этом операторе:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentIQ = from s in _context.Student
select s;
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
При создании или изменении IQueryable
запрос в базу данных не отправляется. Запрос не выполнится, пока объект IQueryable
не будет преобразован в коллекцию. IQueryable
преобразуются в коллекцию путем вызова метода, такого как ToListAsync
. Таким образом, код IQueryable
создает одиночный запрос, который не выполняется до следующего оператора:
Student = await studentIQ.AsNoTracking().ToListAsync();
OnGetAsync
можно расширить на случай большого числа сортируемых столбцов.
Добавление гиперссылок для заголовков столбцов на странице индексов учащихся
Замените код в Students/Index.cshtml
следующем выделенном коде:
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Предыдущий код:
- Добавляет гиперссылки в заголовки столбцов
LastName
иEnrollmentDate
. - Использует эти сведения в
NameSort
иDateSort
для настройки гиперссылок с использованием текущих значений порядка сортировки.
Чтобы проверить работу сортировки, сделайте следующее:
- Запустите приложение и откройте вкладку Students (Учащиеся).
- Щелкните Last Name (Фамилия).
- Щелкните Enrollment Date (Дата зачисления).
Чтобы лучше понять код:
- В
Students/Index.cshtml.cs
задайте точку останова вswitch (sortOrder)
. - Добавьте контрольное значение для
NameSort
иDateSort
. - В
Students/Index.cshtml
задайте точку останова в@Html.DisplayNameFor(model => model.Student[0].LastName)
.
Осуществите пошаговое выполнение в отладчике.
Добавление поля поиска на страницу указателя учащихся
Для добавления фильтрации на страницу индекса учащихся:
- На страницу Razor добавляется текстовое поле и кнопка отправки. Текстовое поле предоставляет строку поиска для имени или фамилии.
- Страничная модель обновляется для использования значения из текстового поля.
Добавление функций фильтрации в метод Index
Измените Students/Index.cshtml.cs
OnGetAsync
, используя следующий код:
public async Task OnGetAsync(string sortOrder, string searchString)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
CurrentFilter = searchString;
IQueryable<Student> studentIQ = from s in _context.Student
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
Предыдущий код:
- Добавляет параметр
searchString
в методOnGetAsync
. Значение строки поиска получается из текстового поля, добавляемого в следующем разделе. - Добавил в оператор LINQ предложение
Where
. Это предложениеWhere
отбирает только учащихся, чье имя или фамилия содержат строку поиска. Оператор LINQ выполняется, только если задано значение для поиска.
Примечание. Предыдущий код вызывает метод Where
объекта IQueryable
, при этом фильтр обрабатывается на сервере. В некоторых случаях приложение может вызывать метод Where
как метод расширения для коллекции в памяти. Например, предположим _context.Students
, что изменения из EF CoreDbSet
метода репозитория, возвращающего коллекцию IEnumerable
. Обычно результат остается прежним, но в некоторых случаях он может отличаться.
Например, реализация Contains
в .NET Framework по умолчанию выполняет сравнение с учетом регистра. В SQL Server Contains
учет регистра определяется параметрами сортировки у экземпляра SQL Server. По умолчанию SQL Server не учитывает регистр. Можно вызвать ToUpper
, чтобы явно велеть тесту не учитывать регистр.
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
Предыдущий код обеспечивает, что результаты не учитывают регистр, если код изменяется для использования IEnumerable
. Когда Contains
вызывается для коллекции IEnumerable
, используется реализация .NET Core. Когда Contains
вызывается для объекта IQueryable
, используется реализация базы данных. При возвращение IEnumerable
из репозитория производительность может значительно снижаться:
- Все строки возвращаются с сервера базы данных.
- Фильтр применяется ко всем возвращенным строкам в приложении.
Вызов ToUpper
снижает производительность. Код ToUpper
добавляет функцию в предложение WHERE TSQL-оператора SELECT. Добавленная функция не позволяет оптимизатору использовать индекс. Учитывая, что SQL устанавливается без учета регистра, рекомендуется не использовать вызов ToUpper
, когда он не требуется.
Добавление поля поиска на страницу индексов учащихся
В Pages/Students/Index.cshtml
поле добавьте следующий выделенный код, чтобы создать кнопку поиска и хром.
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
Для добавления кнопки и поля поиска предыдущий код использует вспомогательную функцию тегов<form>
. По умолчанию вспомогательная функция тегов <form>
отправляет данные формы с помощью POST. При этом параметры передаются в тексте сообщения HTTP, а не в URL-адресе. При использовании HTTP GET данные формы передаются в виде строк запроса в URL-адресе. Передача данных со строками запроса позволяет пользователям добавлять URL-адрес в закладки. Руководства консорциума W3C рекомендуют использовать GET, когда действие не приводит к обновлению.
Проверьте работу приложения:
- Выберите вкладку Students (Учащиеся) и введите строку поиска.
- Нажмите Поиск.
Обратите внимание, что URL-адрес содержит строку поиска.
http://localhost:5000/Students?SearchString=an
Если страница добавлена в закладки, закладка содержит URL-адрес страницы и строку запроса SearchString
. Формирование строки запроса обеспечивает method="get"
в теге form
.
Сейчас при выборе ссылки сортировки заголовка столбца теряется значение фильтра в поле Search (Поиск). Потерянное значение фильтра исправляется в следующем разделе.
Добавление на страницу указателя учащихся разбиения на страницы
В этом разделе создается класс PaginatedList
для поддержки разбиения на страницы. Класс PaginatedList
использует операторы Skip
и Take
для фильтрации данных на сервере вместо того, чтобы извлекать все строки таблицы. На следующем рисунке показаны кнопки перелистывания.
В папке проекта создайте файл PaginatedList.cs
со следующим кодом:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
В предыдущем коде метод CreateAsync
принимает размер и номер страницы и вызывает соответствующие методы Skip
и Take
объекта IQueryable
. Метод ToListAsync
объекта IQueryable
при вызове возвращает список, содержащий только запрошенную страницу. Для включения и отключения кнопок перелистывания страниц Previous (Назад) и Next (Далее) используются свойства HasPreviousPage
и HasNextPage
.
Метод CreateAsync
используется для создания PaginatedList<T>
. Конструктор не позволяет создать объектPaginatedList<T>
, так как конструкторы не могут выполнять асинхронный код.
Добавление разбиения на страницы в метод Index
В Students/Index.cshtml.cs
обновите тип Student
с IList<Student>
на PaginatedList<Student>
:
public PaginatedList<Student> Student { get; set; }
Измените Students/Index.cshtml.cs
OnGetAsync
, используя следующий код:
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
CurrentFilter = searchString;
IQueryable<Student> studentIQ = from s in _context.Student
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
int pageSize = 3;
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
Предыдущий код добавляет страницу индекса, текущий sortOrder
и currentFilter
в сигнатуру метода.
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
Все параметры равны null, когда:
- Страница вызывается по ссылке Students (Учащиеся).
- Пользователь не открывал ссылку перелистывания или сортировки.
При выборе ссылки перелистывания переменная индекса страницы содержит номер страницы для отображения.
CurrentSort
предоставляет странице Razor текущий порядок сортировки. Текущий порядок сортировки нужно включить в ссылки перелистывания, чтобы сохранить его при смене страницы.
CurrentFilter
предоставляет странице Razor текущую строку фильтра. Значение CurrentFilter
:
- Нужно включить в ссылки для перелистывания, чтобы сохранить параметры фильтра при смене страницы.
- Нужно восстановить в текстовом поле после обновления страницы.
Если строка поиска изменяется во время перелистывания, номер страницы сбрасывается в значение 1. Номер страницы должен быть сброшен на 1, так как с новым фильтром может измениться состав отображаемых данных. Если введено значение для поиска и нажата кнопка Submit (Отправить):
- Строка поиска изменяется.
- Значение параметра
searchString
отличается от null.
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
Метод PaginatedList.CreateAsync
преобразует результат запроса учащихся в отдельную страницу коллекции, поддерживающую разбиение на страницы. Эта страница с учащимися передается на страницу Razor.
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
Два вопросительных знака в PaginatedList.CreateAsync
являются оператором объединения с null. Оператор объединения с null определяет значение по умолчанию для типа, допускающего значение null. Выражение (pageIndex ?? 1)
означает возвращение значения pageIndex
, если он имеет значение. Если у pageIndex
нет значения, возвращается 1.
Добавление ссылок на страницу Razor учащихся
Обновите разметку в Students/Index.cshtml
. Изменения выделены:
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@{
var prevDisabled = !Model.Student.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Student.HasNextPage ? "disabled" : "";
}
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>
Ссылки в заголовках столбцов передают в метод OnGetAsync
с помощью строки запроса текущее значение строки поиска, чтобы пользователь мог сортировать отфильтрованные результаты:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
Кнопки перелистывания отображаются вспомогательными функциями тегов:
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>
Запустите приложение и перейдите на страницу учащихся.
- Чтобы убедиться, что разбиение на страницы работает, нажимайте кнопки перелистывания при различном порядке сортировки.
- Чтобы убедиться, что разбиение на страницы работает корректно вместе с сортировкой и фильтрацией, введите строку поиска и попробуйте перелистнуть страницу.
Чтобы лучше понять код:
- В
Students/Index.cshtml.cs
задайте точку останова вswitch (sortOrder)
. - Добавьте контрольное значение для
NameSort
,DateSort
,CurrentSort
иModel.Student.PageIndex
. - В
Students/Index.cshtml
задайте точку останова в@Html.DisplayNameFor(model => model.Student[0].LastName)
.
Осуществите пошаговое выполнение в отладчике.
Изменение страницы "About" (О программе) для отображения статистики учащихся
На этом шаге обновляется, чтобы отобразить количество учащихся, Pages/About.cshtml
зарегистрированных для каждой даты регистрации. Это изменение использует группирование и включает следующие шаги:
- Создание модели представления для данных, используемых страницей About (О программе).
- Обновление страницы "About" (О программе) для использования модели представления.
Создание модели представления
Создайте папку SchoolViewModels в папке Models.
В папке SchoolViewModels добавьте EnrollmentDateGroup.cs
следующий код:
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
public int StudentCount { get; set; }
}
}
Обновление модели страницы "About" (О программе)
Веб-шаблоны в ASP.NET Core 2.2 не включают страницу About. Если вы используете ASP.NET Core 2.2, создайте страницу About Razor Page.
Pages/About.cshtml.cs
Обновите файл со следующим кодом:
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;
namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;
public AboutModel(SchoolContext context)
{
_context = context;
}
public IList<EnrollmentDateGroup> Student { get; set; }
public async Task OnGetAsync()
{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Student
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
Student = await data.AsNoTracking().ToListAsync();
}
}
}
Запрос LINQ группирует записи из таблицы студентов по дате зачисления, вычисляет число записей в каждой группе и сохраняет результаты в коллекцию объектов моделей представления EnrollmentDateGroup
.
Изменение страницы Razor About
Замените код в файле Pages/About.cshtml
следующим кодом:
@page
@model ContosoUniversity.Pages.AboutModel
@{
ViewData["Title"] = "Student Body Statistics";
}
<h2>Student Body Statistics</h2>
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
Запустите приложение и перейдите на страницу "About" (О программе). Количество зачисленных студентов по дням отображается в таблице.
При возникновении проблем, которые вам не удается устранить, скачайте готовое приложение для этого этапа.
Дополнительные ресурсы
В следующем руководстве приложение использует миграции для обновления модели данных.
ASP.NET Core
Кері байланыс
https://aka.ms/ContentUserFeedback.
Жақында қолжетімді болады: 2024 жыл бойы біз GitHub Issues жүйесін мазмұнға арналған кері байланыс механизмі ретінде біртіндеп қолданыстан шығарамыз және оны жаңа кері байланыс жүйесімен ауыстырамыз. Қосымша ақпаратты мұнда қараңыз:Жіберу және пікірді көру