Część 3, Razor strony w EF Core ASP.NET Core — sortowanie, filtrowanie, stronicowanie
Przez Tom Dykstra, Jeremy Likness i Jon P Smith
Aplikacja internetowa Contoso University pokazuje, jak tworzyć Razor aplikacje internetowe stron przy użyciu programu EF Core Visual Studio. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek.
Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację i porównaj ten kod z utworzonymi elementami, wykonując czynności opisane w samouczku.
Ten samouczek dodaje funkcje sortowania, filtrowania i stronicowania na stronach Uczniowie.
Poniższa ilustracja przedstawia ukończoną stronę. Nagłówki kolumn można klikać, aby posortować kolumnę. Kliknij nagłówek kolumny wielokrotnie, aby przełączać się między kolejnością sortowania rosnącego i malejącego.
Dodawanie sortowania
Zastąp kod w Pages/Students/Index.cshtml.cs
pliku poniższym kodem, aby dodać sortowanie.
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();
}
}
Powyższy kod:
- Wymaga dodania
using System;
elementu . - Dodaje właściwości zawierające parametry sortowania.
- Zmienia nazwę
Student
właściwości naStudents
. - Zastępuje kod w metodzie
OnGetAsync
.
Metoda OnGetAsync
odbiera sortOrder
parametr z ciągu zapytania w adresie URL. Adres URL i ciąg zapytania są generowane przez pomocnika tagów zakotwiczenia.
Parametr sortOrder
ma wartość Name
lub Date
. Parametr sortOrder
jest opcjonalny, a następnie określa _desc
kolejność malejącą. Domyślna kolejność sortowania jest rosnąca.
Gdy strona Indeks jest żądana z linku Uczniowie , nie ma ciągu zapytania. Uczniowie są wyświetlani w kolejności rosnącej według nazwiska. Kolejność rosnąca według nazwiska jest default
w instrukcji switch
. Gdy użytkownik kliknie link nagłówka kolumny, odpowiednia sortOrder
wartość zostanie podana w wartości ciągu zapytania.
NameSort
i DateSort
są używane przez Razor stronę do konfigurowania hiperlinków nagłówków kolumn z odpowiednimi wartościami ciągu zapytania:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
Kod używa operatora warunkowego języka C# ?:. Operator ?:
jest trójargumentowym operatorem, przyjmuje trzy operandy. Pierwszy wiersz określa, że gdy sortOrder
wartość null lub jest pusta, NameSort
jest ustawiona na name_desc
wartość . Jeśli sortOrder
wartość nie ma wartości null lub jest pusta, NameSort
jest ustawiona na pusty ciąg.
Te dwie instrukcje umożliwiają stronie ustawienie hiperlinków nagłówka kolumny w następujący sposób:
Bieżąca kolejność sortowania | Hiperłącze nazwiska | Hiperłącze daty |
---|---|---|
Nazwisko rosnąco | malejąco | ascending |
Nazwisko malejące | ascending | ascending |
Data rosnąca | ascending | malejąco |
Data malejąco | ascending | ascending |
Metoda używa linQ to Entities, aby określić kolumnę do sortowania. Kod inicjuje element IQueryable<Student>
przed instrukcją switch i modyfikuje go w instrukcji 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();
Po utworzeniu lub zmodyfikowaniu zapytania IQueryable
nie zostanie wysłane do bazy danych. Zapytanie nie jest wykonywane, dopóki IQueryable
obiekt nie zostanie przekonwertowany na kolekcję. IQueryable
są konwertowane na kolekcję przez wywołanie metody, takiej jak ToListAsync
. IQueryable
W związku z tym kod powoduje utworzenie pojedynczego zapytania, które nie zostanie wykonane, dopóki nie zostanie wykonana następująca instrukcja:
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync
może uzyskać pełne informacje o dużej liczbie kolumn sortowalnych. Aby uzyskać informacje na temat alternatywnego sposobu kodowania tej funkcji, zobacz Używanie dynamicznego LINQ w celu uproszczenia kodu w wersji MVC tej serii samouczków.
Dodawanie hiperlinków nagłówka kolumny do strony Indeks ucznia
Zastąp kod w pliku Students/Index.cshtml
, następującym kodem. Zmiany są wyróżnione.
@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>
Powyższy kod:
- Dodaje hiperlinki do
LastName
nagłówków kolumn iEnrollmentDate
. - Używa informacji w pliku
NameSort
iDateSort
do konfigurowania hiperlinków z bieżącymi wartościami kolejności sortowania. - Zmienia nagłówek strony z Indeksu na Uczniów.
- Zmiany
Model.Student
w plikuModel.Students
.
Aby sprawdzić, czy sortowanie działa:
- Uruchom aplikację i wybierz kartę Uczniowie .
- Kliknij nagłówki kolumn.
Dodawanie filtrowania
Aby dodać filtrowanie do strony Indeks uczniów:
- Pole tekstowe i przycisk przesyłania są dodawane do strony Razor . Pole tekstowe dostarcza ciąg wyszukiwania na pierwszym lub nazwisko.
- Model strony jest aktualizowany w celu użycia wartości pola tekstowego.
Aktualizowanie metody OnGetAsync
Zastąp kod w Students/Index.cshtml.cs
pliku następującym kodem, aby dodać filtrowanie:
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();
}
}
Powyższy kod:
searchString
Dodaje parametr doOnGetAsync
metody i zapisuje wartość parametruCurrentFilter
we właściwości . Wartość ciągu wyszukiwania jest odbierana z pola tekstowego dodanego w następnej sekcji.- Dodaje do instrukcji LINQ klauzulę
Where
. KlauzulaWhere
wybiera tylko uczniów, których imię lub nazwisko zawiera ciąg wyszukiwania. Instrukcja LINQ jest wykonywana tylko wtedy, gdy istnieje wartość do wyszukania.
IQueryable a IEnumerable
Kod wywołuje metodę WhereIQueryable
w obiekcie, a filtr jest przetwarzany na serwerze. W niektórych scenariuszach aplikacja może wywoływać Where
metodę jako metodę rozszerzenia w kolekcji w pamięci. Załóżmy na przykład, że _context.Students
zmiany z EF CoreDbSet
metody repozytorium, która zwraca IEnumerable
kolekcję. Wynik zwykle będzie taki sam, ale w niektórych przypadkach może być inny.
Na przykład implementacja Contains
programu .NET Framework domyślnie wykonuje porównanie z uwzględnieniem wielkości liter. W programie SQL Server Contains
wielkość liter jest określana przez ustawienie sortowania wystąpienia programu SQL Server. Program SQL Server domyślnie nie uwzględnia wielkości liter. Funkcja SQLite domyślnie uwzględnia wielkość liter. ToUpper
można wywołać, aby jawnie bez uwzględniania wielkości liter w teście:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
Powyższy kod gwarantuje, że filtr jest niewrażliwy na wielkość liter, nawet jeśli Where
metoda jest wywoływana w elemecie IEnumerable
SQLite lub jest uruchamiana.
Gdy Contains
jest wywoływana IEnumerable
w kolekcji, jest używana implementacja platformy .NET Core. Po Contains
wywołaniu IQueryable
obiektu jest używana implementacja bazy danych.
Wywoływanie Contains
elementu jest IQueryable
zwykle preferowane ze względu na wydajność. W programie IQueryable
filtrowanie odbywa się przez serwer bazy danych. IEnumerable
Jeśli element zostanie utworzony jako pierwszy, wszystkie wiersze muszą zostać zwrócone z serwera bazy danych.
Istnieje kara za wykonanie wywołania metody ToUpper
. Kod ToUpper
dodaje funkcję w klauzuli WHERE instrukcji TSQL SELECT. Dodano funkcję uniemożliwia optymalizatorowi korzystanie z indeksu. Biorąc pod uwagę, że język SQL jest zainstalowany jako bez uwzględniania wielkości liter, najlepiej jest unikać ToUpper
wywołania, gdy nie jest to konieczne.
Aby uzyskać więcej informacji, zobacz How to use case-insensitive query with Sqlite provider (Jak używać zapytania bez uwzględniania wielkości liter w dostawcy sqlite).
Razor Aktualizowanie strony
Zastąp kod w pliku , Pages/Students/Index.cshtml
aby dodać przycisk Wyszukaj.
@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>
Powyższy kod używa pomocnika tagów do dodania pola tekstowego <form>
i przycisku wyszukiwania. Domyślnie pomocnik tagu <form>
przesyła dane formularza za pomocą wpisu POST. W przypadku funkcji POST parametry są przekazywane w treści komunikatu HTTP, a nie w adresie URL. Gdy jest używany protokół HTTP GET, dane formularza są przekazywane w adresie URL jako ciągi zapytania. Przekazywanie danych za pomocą ciągów zapytania umożliwia użytkownikom tworzenie zakładek adresu URL. Wytyczne dotyczące W3C zalecają użycie polecenia GET, gdy akcja nie spowoduje aktualizacji.
Przetestuj aplikację:
Wybierz kartę Uczniowie i wprowadź ciąg wyszukiwania. Jeśli używasz biblioteki SQLite, filtr nie uwzględnia wielkości liter tylko wtedy, gdy zaimplementowano opcjonalny
ToUpper
kod pokazany wcześniej.Wybierz pozycję Wyszukaj.
Zwróć uwagę, że adres URL zawiera ciąg wyszukiwania. Przykład:
https://localhost:5001/Students?SearchString=an
Jeśli strona jest oznaczona zakładką, zakładka zawiera adres URL strony i SearchString
ciąg zapytania. Element method="get"
w tagu form
jest przyczyną wygenerowania ciągu zapytania.
Obecnie po wybraniu linku sortowania nagłówka kolumny wartość filtru z pola Wyszukiwania zostanie utracona. Utracona wartość filtru jest stała w następnej sekcji.
Dodawanie stronicowania
W tej sekcji jest tworzona PaginatedList
klasa do obsługi stronicowania. Klasa PaginatedList
używa Skip
instrukcji i Take
do filtrowania danych na serwerze zamiast pobierania wszystkich wierszy tabeli. Na poniższej ilustracji przedstawiono przyciski stronicowania.
Tworzenie klasy PaginatedList
W folderze projektu utwórz PaginatedList.cs
za pomocą następującego kodu:
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);
}
}
}
Metoda CreateAsync
w poprzednim kodzie przyjmuje rozmiar strony i numer strony oraz stosuje odpowiednie Skip
instrukcje i Take
do .IQueryable
Gdy ToListAsync
element jest wywoływany w obiekcie IQueryable
, zwraca listę zawierającą tylko żądaną stronę. Właściwości HasPreviousPage
i HasNextPage
służą do włączania lub wyłączania przycisków Wstecz i Dalej stronicowania.
Metoda CreateAsync
jest używana do utworzenia elementu PaginatedList<T>
. Konstruktor nie może utworzyć PaginatedList<T>
obiektu; konstruktory nie mogą uruchamiać kodu asynchronicznego.
Dodawanie rozmiaru strony do konfiguracji
Dodaj PageSize
do appsettings.json
pliku konfiguracji :
{
"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"
}
}
Dodawanie stronicowania do modelu IndexModel
Zastąp kod w pliku , Students/Index.cshtml.cs
aby dodać stronicowanie.
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);
}
}
}
Powyższy kod:
- Zmienia typ
Students
właściwości zIList<Student>
naPaginatedList<Student>
. - Dodaje indeks strony, bieżący
sortOrder
element i elementcurrentFilter
doOnGetAsync
podpisu metody. - Zapisuje kolejność sortowania
CurrentSort
we właściwości . - Resetuje indeks strony do wartości 1, gdy istnieje nowy ciąg wyszukiwania.
- Używa klasy do uzyskiwania
PaginatedList
jednostek Student. - Ustawia
pageSize
wartość 3 z konfiguracji, 4, jeśli konfiguracja nie powiedzie się.
Wszystkie odbierane OnGetAsync
parametry mają wartość null, gdy:
- Strona jest wywoływana z linku Uczniowie .
- Użytkownik nie kliknął łącza stronicowania ani sortowania.
Po kliknięciu linku stronicowania zmienna indeksu strony zawiera numer strony do wyświetlenia.
Właściwość CurrentSort
udostępnia stronę Razor z bieżącą kolejnością sortowania. Bieżąca kolejność sortowania musi być uwzględniona w linkach stronicowania, aby zachować kolejność sortowania podczas stronicowania.
Właściwość CurrentFilter
udostępnia stronę Razor z bieżącym ciągiem filtru. Wartość CurrentFilter
:
- Aby zachować ustawienia filtru podczas stronicowania, należy dołączyć do linków stronicowania.
- Należy przywrócić do pola tekstowego po ponownym uruchomieniu strony.
Jeśli ciąg wyszukiwania zostanie zmieniony podczas stronicowania, strona zostanie zresetowana do wartości 1. Strona musi zostać zresetowana do wartości 1, ponieważ nowy filtr może spowodować wyświetlenie różnych danych. Po wprowadzeniu wartości wyszukiwania i wybraniu opcji Prześlij :
- Ciąg wyszukiwania jest zmieniany.
- Parametr
searchString
nie ma wartości null.
Metoda PaginatedList.CreateAsync
konwertuje zapytanie ucznia na jedną stronę uczniów w typie kolekcji, który obsługuje stronicowanie. Ta pojedyncza strona uczniów jest przekazywana Razor do strony.
Dwa znaki zapytania po pageIndex
wywołaniu PaginatedList.CreateAsync
reprezentują operator łączenia wartości null. Operator łączenia wartości null definiuje wartość domyślną dla typu dopuszczającego wartość null. Wyrażenie pageIndex ?? 1
zwraca wartość pageIndex
, jeśli ma wartość, w przeciwnym razie zwraca wartość 1.
Dodawanie łączy stronicowania
Zamień kod w pliku Students/Index.cshtml
na następujący kod. Zmiany są wyróżnione:
@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>
Linki nagłówka kolumny używają ciągu zapytania, aby przekazać bieżący ciąg wyszukiwania do OnGetAsync
metody :
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
Przyciski stronicowania są wyświetlane przez pomocników tagów:
<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>
Uruchom aplikację i przejdź do strony uczniowie.
- Aby upewnić się, że stronicowanie działa, kliknij łącza stronicowania w różnych kolejności sortowania.
- Aby sprawdzić, czy stronicowanie działa prawidłowo w przypadku sortowania i filtrowania, wprowadź ciąg wyszukiwania i spróbuj stronicować.
Grupowanie
W tej sekcji zostanie utworzona strona zawierająca About
liczbę uczniów zarejestrowanych dla każdej daty rejestracji. Aktualizacja używa grupowania i obejmuje następujące kroki:
- Utwórz model widoku dla danych używanych
About
przez stronę. - Zaktualizuj stronę,
About
aby korzystała z modelu widoku.
Tworzenie modelu widoku
Utwórz folder Models/SchoolViewModels.
Utwórz SchoolViewModels/EnrollmentDateGroup.cs
za pomocą następującego kodu:
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 Tworzenie strony
Pages/About.cshtml
Utwórz plik z następującym kodem:
@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>
Tworzenie modelu strony
Pages/About.cshtml.cs
Zaktualizuj plik przy użyciu następującego kodu:
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();
}
}
}
Instrukcja LINQ grupuje jednostki uczniów według daty rejestracji, oblicza liczbę jednostek w każdej grupie i przechowuje wyniki w kolekcji EnrollmentDateGroup
obiektów modelu widoku.
Uruchom aplikację i przejdź do strony Informacje. Liczba uczniów dla każdej daty rejestracji jest wyświetlana w tabeli.
Następne kroki
W następnym samouczku aplikacja używa migracji w celu zaktualizowania modelu danych.
W tym samouczku dodano funkcje sortowania, filtrowania, grupowania i stronicowania.
Poniższa ilustracja przedstawia ukończoną stronę. Nagłówki kolumn można klikać, aby posortować kolumnę. Kliknięcie nagłówka kolumny wielokrotnie przełącza się między kolejnością sortowania rosnącego i malejącego.
Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację.
Dodawanie sortowania do strony Indeks
Dodaj ciągi do elementu Students/Index.cshtml.cs
PageModel
, aby zawierały parametry sortowania:
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; }
Zaktualizuj element za Students/Index.cshtml.cs
OnGetAsync
pomocą następującego kodu:
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();
}
Powyższy kod odbiera sortOrder
parametr z ciągu zapytania w adresie URL. Adres URL (w tym ciąg zapytania) jest generowany przez pomocnika tagów zakotwiczenia
Parametr sortOrder
ma wartość "Name" lub "Date". Parametr sortOrder
jest opcjonalny, po którym następuje "_desc", aby określić kolejność malejącą. Domyślna kolejność sortowania jest rosnąca.
Gdy strona Indeks jest żądana z linku Uczniowie , nie ma ciągu zapytania. Uczniowie są wyświetlani w kolejności rosnącej według nazwiska. Kolejność rosnąca według nazwiska to domyślna (przypadek rezerwowy) w instrukcji switch
. Gdy użytkownik kliknie link nagłówka kolumny, odpowiednia sortOrder
wartość zostanie podana w wartości ciągu zapytania.
NameSort
i DateSort
są używane przez Razor stronę do konfigurowania hiperlinków nagłówków kolumn z odpowiednimi wartościami ciągu zapytania:
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();
}
Poniższy kod zawiera warunkowy operator języka C#?:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
Pierwszy wiersz określa, NameSort
że w przypadku sortOrder
wartości null lub pustej jest ustawiona wartość "name_desc". Jeśli sortOrder
wartość nie ma wartości null lub jest pusta, NameSort
jest ustawiona na pusty ciąg.
Jest ?: operator
również znany jako operatorternary.
Te dwie instrukcje umożliwiają stronie ustawienie hiperlinków nagłówka kolumny w następujący sposób:
Bieżąca kolejność sortowania | Hiperłącze nazwiska | Hiperłącze daty |
---|---|---|
Nazwisko rosnąco | malejąco | ascending |
Nazwisko malejące | ascending | ascending |
Data rosnąca | ascending | malejąco |
Data malejąco | ascending | ascending |
Metoda używa linQ to Entities, aby określić kolumnę do sortowania. Kod inicjuje element IQueryable<Student>
przed instrukcją switch i modyfikuje go w instrukcji 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();
}
Po utworzeniu lub zmodyfikowaniu zapytaniaIQueryable
nie zostanie wysłane do bazy danych. Zapytanie nie jest wykonywane, dopóki IQueryable
obiekt nie zostanie przekonwertowany na kolekcję. IQueryable
są konwertowane na kolekcję przez wywołanie metody, takiej jak ToListAsync
. IQueryable
W związku z tym kod powoduje utworzenie pojedynczego zapytania, które nie zostanie wykonane, dopóki nie zostanie wykonana następująca instrukcja:
Student = await studentIQ.AsNoTracking().ToListAsync();
OnGetAsync
może uzyskać pełne informacje o dużej liczbie kolumn sortowalnych.
Dodawanie hiperlinków nagłówka kolumny do strony Indeks ucznia
Zastąp kod w pliku Students/Index.cshtml
następującym wyróżnionym kodem:
@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>
Powyższy kod:
- Dodaje hiperlinki do
LastName
nagłówków kolumn iEnrollmentDate
. - Używa informacji w pliku
NameSort
iDateSort
do konfigurowania hiperlinków z bieżącymi wartościami kolejności sortowania.
Aby sprawdzić, czy sortowanie działa:
- Uruchom aplikację i wybierz kartę Uczniowie .
- Kliknij pozycję Nazwisko.
- Kliknij pozycję Data rejestracji.
Aby lepiej zrozumieć kod:
- W
Students/Index.cshtml.cs
programie ustaw punkt przerwania na .switch (sortOrder)
- Dodaj zegarek dla
NameSort
iDateSort
. - W
Students/Index.cshtml
programie ustaw punkt przerwania na .@Html.DisplayNameFor(model => model.Student[0].LastName)
Wykonaj kroki debugera.
Dodawanie pola wyszukiwania do strony Indeks uczniów
Aby dodać filtrowanie do strony Indeks uczniów:
- Pole tekstowe i przycisk przesyłania są dodawane do strony Razor . Pole tekstowe dostarcza ciąg wyszukiwania na pierwszym lub nazwisko.
- Model strony jest aktualizowany w celu użycia wartości pola tekstowego.
Dodawanie funkcji filtrowania do metody Index
Zaktualizuj element za Students/Index.cshtml.cs
OnGetAsync
pomocą następującego kodu:
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();
}
Powyższy kod:
searchString
Dodaje parametr doOnGetAsync
metody . Wartość ciągu wyszukiwania jest odbierana z pola tekstowego dodanego w następnej sekcji.- Dodano do instrukcji LINQ klauzulę
Where
. KlauzulaWhere
wybiera tylko uczniów, których imię lub nazwisko zawiera ciąg wyszukiwania. Instrukcja LINQ jest wykonywana tylko wtedy, gdy istnieje wartość do wyszukania.
Uwaga: Powyższy kod wywołuje Where
metodę IQueryable
na obiekcie, a filtr jest przetwarzany na serwerze. W niektórych scenariuszach aplikacja może wywoływać Where
metodę jako metodę rozszerzenia w kolekcji w pamięci. Załóżmy na przykład, że _context.Students
zmiany z EF CoreDbSet
metody repozytorium, która zwraca IEnumerable
kolekcję. Wynik zwykle będzie taki sam, ale w niektórych przypadkach może być inny.
Na przykład implementacja Contains
programu .NET Framework domyślnie wykonuje porównanie z uwzględnieniem wielkości liter. W programie SQL Server Contains
wielkość liter jest określana przez ustawienie sortowania wystąpienia programu SQL Server. Program SQL Server domyślnie nie uwzględnia wielkości liter. ToUpper
można wywołać, aby jawnie bez uwzględniania wielkości liter w teście:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
Powyższy kod gwarantuje, że wyniki są niewrażliwe na wielkość liter, jeśli kod zmieni się w celu użycia elementu IEnumerable
. Gdy Contains
jest wywoływana IEnumerable
w kolekcji, jest używana implementacja platformy .NET Core. Po Contains
wywołaniu IQueryable
obiektu jest używana implementacja bazy danych. Zwracanie elementu IEnumerable
z repozytorium może mieć znaczącą karę za wydajność:
- Wszystkie wiersze są zwracane z serwera bazy danych.
- Filtr jest stosowany do wszystkich zwracanych wierszy w aplikacji.
Istnieje kara za wykonanie wywołania metody ToUpper
. Kod ToUpper
dodaje funkcję w klauzuli WHERE instrukcji TSQL SELECT. Dodano funkcję uniemożliwia optymalizatorowi korzystanie z indeksu. Biorąc pod uwagę, że język SQL jest zainstalowany jako bez uwzględniania wielkości liter, najlepiej jest unikać ToUpper
wywołania, gdy nie jest to konieczne.
Dodawanie pola wyszukiwania do strony Indeks ucznia
W Pages/Students/Index.cshtml
pliku dodaj następujący wyróżniony kod, aby utworzyć przycisk Wyszukaj i rozdzielony chrome.
@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">
Powyższy kod używa pomocnika tagów do dodania pola tekstowego <form>
i przycisku wyszukiwania. Domyślnie pomocnik tagu <form>
przesyła dane formularza za pomocą wpisu POST. W przypadku funkcji POST parametry są przekazywane w treści komunikatu HTTP, a nie w adresie URL. Gdy jest używany protokół HTTP GET, dane formularza są przekazywane w adresie URL jako ciągi zapytania. Przekazywanie danych za pomocą ciągów zapytania umożliwia użytkownikom tworzenie zakładek adresu URL. Wytyczne dotyczące W3C zalecają użycie polecenia GET, gdy akcja nie spowoduje aktualizacji.
Przetestuj aplikację:
- Wybierz kartę Uczniowie i wprowadź ciąg wyszukiwania.
- Wybierz pozycję Wyszukaj.
Zwróć uwagę, że adres URL zawiera ciąg wyszukiwania.
http://localhost:5000/Students?SearchString=an
Jeśli strona jest oznaczona zakładką, zakładka zawiera adres URL strony i SearchString
ciąg zapytania. Element method="get"
w tagu form
jest przyczyną wygenerowania ciągu zapytania.
Obecnie po wybraniu linku sortowania nagłówka kolumny wartość filtru z pola Wyszukiwania zostanie utracona. Utracona wartość filtru jest stała w następnej sekcji.
Dodawanie funkcji stronicowania do strony Indeks uczniów
W tej sekcji jest tworzona PaginatedList
klasa do obsługi stronicowania. Klasa PaginatedList
używa Skip
instrukcji i Take
do filtrowania danych na serwerze zamiast pobierania wszystkich wierszy tabeli. Na poniższej ilustracji przedstawiono przyciski stronicowania.
W folderze projektu utwórz PaginatedList.cs
za pomocą następującego kodu:
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);
}
}
}
Metoda CreateAsync
w poprzednim kodzie przyjmuje rozmiar strony i numer strony oraz stosuje odpowiednie Skip
instrukcje i Take
do .IQueryable
Gdy ToListAsync
element jest wywoływany w obiekcie IQueryable
, zwraca listę zawierającą tylko żądaną stronę. Właściwości HasPreviousPage
i HasNextPage
służą do włączania lub wyłączania przycisków Wstecz i Dalej stronicowania.
Metoda CreateAsync
jest używana do utworzenia elementu PaginatedList<T>
. Konstruktor nie może utworzyć PaginatedList<T>
obiektu, konstruktory nie mogą uruchamiać kodu asynchronicznego.
Dodawanie funkcji stronicowania do metody Index
W Students/Index.cshtml.cs
pliku zaktualizuj typ parametru Student
od IList<Student>
do PaginatedList<Student>
:
public PaginatedList<Student> Student { get; set; }
Zaktualizuj element za Students/Index.cshtml.cs
OnGetAsync
pomocą następującego kodu:
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);
}
Powyższy kod dodaje indeks strony, bieżący sortOrder
element i parametr currentFilter
do podpisu metody.
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
Wszystkie parametry mają wartość null, gdy:
- Strona jest wywoływana z linku Uczniowie .
- Użytkownik nie kliknął łącza stronicowania ani sortowania.
Po kliknięciu linku stronicowania zmienna indeksu strony zawiera numer strony do wyświetlenia.
CurrentSort
udostępnia stronę Razor z bieżącą kolejnością sortowania. Bieżąca kolejność sortowania musi być uwzględniona w linkach stronicowania, aby zachować kolejność sortowania podczas stronicowania.
CurrentFilter
Udostępnia stronę Razor z bieżącym ciągiem filtru. Wartość CurrentFilter
:
- Aby zachować ustawienia filtru podczas stronicowania, należy dołączyć do linków stronicowania.
- Należy przywrócić do pola tekstowego po ponownym uruchomieniu strony.
Jeśli ciąg wyszukiwania zostanie zmieniony podczas stronicowania, strona zostanie zresetowana do wartości 1. Strona musi zostać zresetowana do wartości 1, ponieważ nowy filtr może spowodować wyświetlenie różnych danych. Po wprowadzeniu wartości wyszukiwania i wybraniu opcji Prześlij :
- Ciąg wyszukiwania jest zmieniany.
- Parametr
searchString
nie ma wartości null.
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
Metoda PaginatedList.CreateAsync
konwertuje zapytanie ucznia na jedną stronę uczniów w typie kolekcji, który obsługuje stronicowanie. Ta pojedyncza strona uczniów jest przekazywana Razor do strony.
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
Dwa znaki zapytania w obiekcie PaginatedList.CreateAsync
reprezentują operator łączenia wartości null. Operator łączenia wartości null definiuje wartość domyślną dla typu dopuszczającego wartość null. Wyrażenie (pageIndex ?? 1)
oznacza zwrócenie wartości , pageIndex
jeśli ma wartość. Jeśli pageIndex
nie ma wartości, zwróć wartość 1.
Dodawanie linków stronicowania do strony ucznia Razor
Zaktualizuj znaczniki w pliku Students/Index.cshtml
. Zmiany są wyróżnione:
@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>
Linki nagłówka kolumny używają ciągu zapytania, aby przekazać bieżący ciąg wyszukiwania do OnGetAsync
metody, aby użytkownik mógł sortować wyniki filtru:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
Przyciski stronicowania są wyświetlane przez pomocników tagów:
<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>
Uruchom aplikację i przejdź do strony uczniowie.
- Aby upewnić się, że stronicowanie działa, kliknij łącza stronicowania w różnych kolejności sortowania.
- Aby sprawdzić, czy stronicowanie działa prawidłowo w przypadku sortowania i filtrowania, wprowadź ciąg wyszukiwania i spróbuj stronicować.
Aby lepiej zrozumieć kod:
- W
Students/Index.cshtml.cs
programie ustaw punkt przerwania na .switch (sortOrder)
- Dodaj zegarek dla ,
NameSort
,CurrentSort
DateSort
iModel.Student.PageIndex
. - W
Students/Index.cshtml
programie ustaw punkt przerwania na .@Html.DisplayNameFor(model => model.Student[0].LastName)
Wykonaj kroki debugera.
Aktualizowanie strony Informacje w celu wyświetlenia statystyk uczniów
W tym kroku zostanie zaktualizowany, Pages/About.cshtml
aby wyświetlić liczbę uczniów zarejestrowanych dla każdej daty rejestracji. Aktualizacja używa grupowania i obejmuje następujące kroki:
- Utwórz model widoku dla danych używanych przez stronę informacje .
- Zaktualizuj stronę Informacje, aby użyć modelu widoku.
Tworzenie modelu widoku
Utwórz folder SchoolViewModels w folderze Models.
W folderze SchoolViewModels dodaj element EnrollmentDateGroup.cs
z następującym kodem:
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; }
}
}
Aktualizowanie modelu strony Informacje
Szablony internetowe w programie ASP.NET Core 2.2 nie zawierają strony Informacje. Jeśli używasz ASP.NET Core 2.2, utwórz stronę informacje Razor .
Pages/About.cshtml.cs
Zaktualizuj plik przy użyciu następującego kodu:
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();
}
}
}
Instrukcja LINQ grupuje jednostki uczniów według daty rejestracji, oblicza liczbę jednostek w każdej grupie i przechowuje wyniki w kolekcji EnrollmentDateGroup
obiektów modelu widoku.
Modyfikowanie strony Informacje Razor
Zastąp kod w Pages/About.cshtml
pliku następującym kodem:
@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>
Uruchom aplikację i przejdź do strony Informacje. Liczba uczniów dla każdej daty rejestracji jest wyświetlana w tabeli.
Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację dla tego etapu.
Dodatkowe zasoby
W następnym samouczku aplikacja używa migracji w celu zaktualizowania modelu danych.
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla