Teil 3: Razor-Seiten mit EF Core in ASP.NET Core – Sortieren, Filtern und Paging
Von Tom Dykstra, Jeremy Likness und Jon P. Smith
Die Web-App Contoso University veranschaulicht, wie Razor-Seiten-Web-Apps mit EF Core und Visual Studio erstellt werden können. Informationen zu den Tutorials finden Sie im ersten Tutorial.
Wenn Probleme auftreten, die Sie nicht beheben können, laden Sie die vollständige App herunter, und vergleichen Sie diesen Code mit dem Code, den Sie anhand des Tutorials erstellt haben.
In diesem Tutorial werden Funktionen zum Sortieren und Filtern sowie für Paging zur Student-Seiten hinzugefügt.
Die folgende Abbildung zeigt eine fertige Seite. Die Spaltenüberschriften sind Links, die zum Sortieren der Spalte angeklickt werden können. Wiederholtes Klicken auf eine Spaltenüberschrift schaltet zwischen aufsteigender und absteigender Sortierreihenfolge um.
Hinzufügen von Sortierung
Ersetzen Sie den Code in Pages/Students/Index.cshtml.cs
durch den folgenden Code, um Sortierung hinzuzufügen.
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();
}
}
Der vorangehende Code:
- Er erfordert das Hinzufügen von
using System;
. - Fügt Eigenschaften hinzu, die die Sortierparameter enthalten.
- Ändert den Namen der
Student
-Eigenschaft inStudents
. - Ersetzt den Code in der
OnGetAsync
-Methode.
Die OnGetAsync
-Methode empfängt einen sortOrder
-Parameter aus der Abfragezeichenfolge in der URL. Die URL und die Abfragezeichenfolge werden vom Anchor-Taghilfsprogramm generiert.
Der sortOrder
-Parameter ist entweder Name
oder Date
. Dem Parameter sortOrder
folgt optional _desc
zur Angabe einer absteigenden Reihenfolge. Standardmäßig wird eine aufsteigende Sortierreihenfolge verwendet.
Wenn die Indexseite über den Link Studenten angefordert wird, gibt es keine Abfragezeichenfolge. Die Studenten werden in aufsteigender Reihenfolge nach Nachnamen angezeigt. Die aufsteigende Reihenfolge nach Nachnamen ist default
in der switch
-Anweisung. Wenn der Benutzer auf einen Link in einer Spaltenüberschrift klickt, wird der entsprechende sortOrder
-Wert im Abfragezeichenfolgenwert bereitgestellt.
NameSort
und DateSort
werden auf der Razor-Seite verwendet, um die Hyperlinks in den Spaltenüberschriften mit den entsprechenden Abfragezeichenfolgenwerten zu konfigurieren:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
Der Code verwendet den bedingten C#-Operator ?:. Der Operator ?:
ist ein ternärer Operator (er nimmt drei Operanden an). Die erste Zeile gibt an, dass wenn sortOrder
NULL oder leer ist, NameSort
auf name_desc
festgelegt wird. Wenn sortOrder
nicht NULL oder leer ist, wird NameSort
auf eine leere Zeichenfolge festgelegt.
Durch diese beiden Anweisungen können auf der Seite die Hyperlinks in den Spaltenüberschriften wie folgt festgelegt werden:
Aktuelle Sortierreihenfolge | Hyperlink „Nachname“ | Hyperlink „Datum“ |
---|---|---|
Nachname (aufsteigend) | descending | ascending |
Nachname (absteigend) | ascending | ascending |
Datum (aufsteigend) | ascending | descending |
Datum (absteigend) | ascending | ascending |
Die Methode gibt über LINQ to Entities die Spalte an, nach der sortiert werden soll. Der Code initialisiert das Objekt IQueryable<Student>
vor der Switch-Anweisung und ändert es in die Switch-Anweisung:
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();
Wenn ein Objekt vom Typ IQueryable
erstellt oder geändert wird, wird keine Abfrage an die Datenbank gesendet. Die Abfrage wird erst dann ausgeführt, wenn das Objekt IQueryable
in eine Sammlung konvertiert wurde. IQueryable
werden in eine Sammlung konvertiert, indem eine Methode wie z.B. ToListAsync
aufgerufen wird. Aus diesem Grund führt der IQueryable
-Code zu einer einzelnen Abfrage, die bis zu folgender Anweisung nicht ausgeführt wird:
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync
könnte mit einer Vielzahl von sortierbaren Spalten ausführlich werden. Informationen zu einer alternativen Möglichkeit zum Programmieren dieser Funktionalität finden Sie unter Verwenden von dynamischem LINQ zum Vereinfachen von Code in der MVC-Version dieser Tutorialreihe.
Hinzufügen von Hyperlinks auf Spaltenüberschriften zur Studentenindexseite
Ersetzen Sie den Code in Students/Index.cshtml
durch folgenden Code. Die Änderungen werden hervorgehoben.
@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>
Der vorangehende Code:
- Fügt Hyperlinks zu den Spaltenüberschriften
LastName
undEnrollmentDate
hinzu. - Verwendet die Informationen in
NameSort
undDateSort
zum Einrichten von Hyperlinks mit den Werten der aktuellen Sortierreihenfolge. - Ändert die Seitenüberschrift aus „Index“ in „Students“.
- Ändert
Model.Student
inModel.Students
.
So überprüfen Sie die Funktionsfähigkeit der Sortierung:
- Führen Sie die App aus, und klicken Sie auf die Registerkarte Studenten.
- Klicken Sie auf die Spaltenüberschriften.
Hinzufügen von Filterung
So fügen Sie Filter zur Indexseite „Studenten“ hinzu:
- Ein Textfeld und die Schaltfläche „Senden“ werden zur Razor Page hinzugefügt. Im Textfeld wird eine Suchzeichenfolge für den Vor- oder Nachnamen bereitgestellt.
- Das Seitenmodell wird aktualisiert, damit der Wert im Textfeld verwendet wird.
Aktualisieren der OnGetAsync-Methode
Ersetzen Sie den Code in Students/Index.cshtml.cs
durch den folgenden Code, um Filterung hinzuzufügen:
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();
}
}
Der vorangehende Code:
- Fügt der
OnGetAsync
-Methode densearchString
-Parameter hinzu und speichert den Parameterwert in derCurrentFilter
-Eigenschaft. Der Suchzeichenfolgenwert wird aus einem Textfeld empfangen, das im nächsten Abschnitt hinzugefügt wird. - Fügt der LINQ-Anweisung eine
Where
-Klausel hinzu. In derWhere
-Klausel werden nur die Studenten ausgewählt, deren Vor- oder Nachname die Suchzeichenfolge enthält. Die LINQ-Anweisung wird nur dann ausgeführt, wenn ein Wert vorhanden ist, nach dem gesucht werden soll.
IQueryable im Vergleich zu IEnumerable
Der Code ruft die Methode Where in einem IQueryable
-Objekt auf, und der Filter wird auf dem Server verarbeitet. In einigen Szenarios ruft die App möglicherweise die Methode Where
als Erweiterungsmethode für eine Auflistung im Speicher auf. Angenommen, _context.Students
wird beispielsweise von EF CoreDbSet
in eine Repositorymethode geändert, die eine IEnumerable
-Sammlung zurückgibt. Das Ergebnis wäre normalerweise identisch, aber in einigen Fällen kann es unterschiedlich ausfallen.
Bei der .NET Framework-Implementierung von Contains
wird beispielsweise standardmäßig ein Vergleich unter Beachtung der Groß-/Kleinschreibung vorgenommen. In SQL Server wird die Unterscheidung von Groß-/Kleinschreibung bei Contains
durch die Einstellung für die Sortierung der SQL Server-Instanz bestimmt. Bei SQL Server wird die Groß-/Kleinschreibung standardmäßig nicht beachtet. Für SQLite wird standardmäßig zwischen Groß-/Kleinschreibung unterschieden. ToUpper
könnte aufgerufen werden, wenn die Groß-/Kleinschreibung bei der Durchführung des Tests explizit nicht beachtet werden soll:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
Der vorstehende Code würde sicherstellen, dass der Filter nicht zwischen Groß-/Kleinschreibung unterscheidet, auch wenn die Where
-Methode für IEnumerable
aufgerufen oder für SQLite ausgeführt wird.
Wenn Contains
in einer IEnumerable
-Sammlung verwendet wird, wird die .NET Core-Implementierung verwendet. Wenn Contains
in einem IQueryable
-Objekt aufgerufen wird, wird die Datenbankimplementierung verwendet.
Der Aufruf von Contains
für IQueryable
ist in der Regel aus Leistungsgründen vorzuziehen. Mit IQueryable
wird die Filterung vom Datenbankserver ausgeführt. Wenn zuerst ein IEnumerable
erstellt wird, müssen alle Zeilen vom Datenbankserver zurückgegeben werden.
Beim Aufrufen von ToUpper
kommt es zu Leistungseinbußen. Der ToUpper
-Code in der WHERE-Klausel der TSQL SELECT-Anweisung eine Funktion hinzu. Durch die hinzugefügte Funktion wird verhindert, dass der Optimierer einen Index verwendet. Da in SQL die Groß-/Kleinschreibung beachtet wird, sollte ToUpper
möglichst nicht aufgerufen werden, wenn dies nicht notwendig ist.
Weitere Informationen finden Sie unter Verwenden von Abfragen ohne Berücksichtigung der Groß-/Kleinschreibung mit dem SQLite-Anbieter.
Aktualisieren der Razor-Seite
Ersetzen Sie den Code in Pages/Students/Index.cshtml
, um eine Schaltfläche Search (Suchen) hinzuzufügen.
@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>
Der vorangehende Code verwendet das <form>
-Taghilfsprogramm, um das Suchtextfeld und die Schaltfläche hinzuzufügen. Das <form>
-Taghilfsprogramm sendet Formulardaten standardmäßig mit einer POST-Anforderung. Mit der POST-Anforderung werden die Parameter im HTTP-Nachrichtentext und nicht in der URL übergeben. Bei Verwendung einer HTTP GET-Anforderung werden die Formulardaten als Abfragezeichenfolgen in der URL übergeben. Durch die Übergabe der Daten mit Abfragezeichenfolgen können Benutzer die URL als Lesezeichen speichern. In den W3C-Richtlinien wird die Verwendung einer GET-Anforderung empfohlen, wenn die Aktion nicht zu einem Update führt.
Testen der App:
Klicken Sie auf die Registerkarte Studenten, und geben Sie eine Suchzeichenfolge ein. Wenn Sie SQLite verwenden, wird für den Filter nur denn nicht zwischen Groß-/Kleinschreibung unterschieden, wenn Sie den zuvor gezeigten optionalen
ToUpper
-Code implementiert haben.Klicken Sie auf Suchen.
Beachten Sie, dass die URL die Suchzeichenfolge enthält. Zum Beispiel:
https://localhost:5001/Students?SearchString=an
Wenn die Seite als Lesezeichen gespeichert wird, enthält das Lesezeichen die URL der Seite und die SearchString
-Abfragezeichenfolge. Durch method="get"
im form
-Tag wurde die Abfragezeichenfolge generiert.
Aktuell geht der Filterwert aus dem Feld Suchen verloren, wenn ein Link zur Sortierung der Spaltenüberschrift ausgewählt wird. Das Problem des verlorenen Filterwerts wird im nächsten Abschnitt behoben.
Hinzufügen von Paging
In diesem Abschnitt wird eine PaginatedList
-Klasse zur Unterstützung von Paging erstellt. Die PaginatedList
-Klasse verwendet Skip
- und Take
-Anweisungen zum Filtern von Daten auf dem Server, statt alle Zeilen der Tabelle abzurufen. Die folgende Abbildung zeigt die Pagingschaltflächen.
Erstellen der PaginatedList-Klasse
Erstellen Sie im Projektordner PaginatedList.cs
mit folgendem Code:
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);
}
}
}
Die Methode CreateAsync
im vorangehenden Code übernimmt die Seitengröße und die Seitenzahl und wendet die entsprechenden Skip
- und Take
-Anweisungen auf das Objekt IQueryable
an. Wenn ToListAsync
im Objekt IQueryable
aufgerufen wird, wird eine Liste zurückgegeben, die nur die angeforderte Seite enthält. Die Eigenschaften HasPreviousPage
und HasNextPage
dienen zum Aktivieren oder Deaktivieren der Schaltflächen Zurück und Weiter für das Paging.
Die Methode CreateAsync
dient zum Erstellen des Objekts PaginatedList<T>
. Ein Konstruktor kann das Objekt PaginatedList<T>
nicht erstellen. Konstruktoren können keinen asynchronen Code ausführen.
Hinzufügen der Seitengröße zur Konfiguration
Fügen Sie PageSize
der appsettings.json
Konfigurationsdatei hinzu:
{
"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"
}
}
Hinzufügen von Paging zu IndexModel
Ersetzen Sie den Code in Students/Index.cshtml.cs
, um Paging hinzuzufügen.
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);
}
}
}
Der vorangehende Code:
- Ändert den Typ der
Students
-Eigenschaft ausIList<Student>
inPaginatedList<Student>
. - Fügt den Seitenindex, die aktuelle
sortOrder
und dencurrentFilter
zurOnGetAsync
-Methodensignatur hinzu. - Er speichert die Sortierreihenfolge in der
CurrentSort
-Eigenschaft. - Setzt den Seitenindex auf 1 zurück, wenn eine neue Suchzeichenfolge vorhanden ist.
- Verwendet die
PaginatedList
-Klasse zum Abrufen von Student-Entitäten. - Legt
pageSize
auf 3 aus Konfiguration fest, 4m wenn die Konfiguration fehlschlägt.
Alle Parameter, die OnGetAsync
empfängt, sind NULL, wenn:
- Die Seite über den Link Studenten aufgerufen wird.
- Der Benutzer auf keinen Link für das Paging oder die Sortierung geklickt hat.
Wenn auf einen Paging-Link geklickt wird, enthält die Seitenindexvariable die anzuzeigende Seitenzahl.
Die CurrentSort
-Eigenschaft stellt die Razor-Seite mit der aktuellen Sortierreihenfolge bereit. Die aktuelle Sortierreihenfolge muss in den Paging-Links enthalten sein, damit die Sortierreihenfolge während des Pagings beibehalten wird.
Die CurrentFilter
-Eigenschaft stellt die Razor-Seite mit der aktuellen Filterzeichenfolge bereit. Der CurrentFilter
-Wert:
- Muss in den Paging-Links enthalten sein, damit die Filtereinstellungen während des Pagings beibehalten werden.
- Muss im Textfeld wiederhergestellt werden, wenn die Seite erneut angezeigt wird.
Wenn die Suchzeichenfolge während des Pagings geändert wird, wird die Seite auf 1 zurückgesetzt. Die Seite muss auf 1 zurückgesetzt werden, da der neue Filter andere Daten anzeigen kann. Folgendes gilt, wenn ein Suchwert eingegeben und auf Senden geklickt wird:
- Die Suchzeichenfolge wird geändert.
- Der Parameter
searchString
ist nicht NULL.
Die Methode PaginatedList.CreateAsync
konvertiert die Studentenabfrage in eine einzelne Studentenseite in einem Sammlungstyp, der das Paging unterstützt. Diese einzelne Studentenseite wird an die Razor Page übergeben.
Die zwei Fragezeichen nach pageIndex
im PaginatedList.CreateAsync
-Aufruf stellen den NULL-Sammeloperator dar. Der NULL-Sammeloperator definiert einen Standardwert für einen auf NULL festlegbaren Typ. Der Ausdruck pageIndex ?? 1
gibt den Wert pageIndex
zurück, wenn er über einen Wert verfügt, andernfalls wird 1 zurückgegeben.
Hinzufügen von Paginglinks
Ersetzen Sie den Code in Students/Index.cshtml
durch folgenden Code. Die Änderungen werden hervorgehoben:
@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>
In den Links der Spaltenüberschriften wird die Abfragezeichenfolge verwendet, um die aktuelle Suchzeichenfolge an die Methode OnGetAsync
zu übergeben:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
Die Pagingschaltflächen werden durch Taghilfsprogramme angezeigt:
<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>
Führen Sie die App aus, und navigieren Sie zur Studentenseite.
- Klicken Sie in verschiedenen Sortierreihenfolgen auf die Paging-Links, um sicherzustellen, dass das Paging funktioniert.
- Geben Sie eine Suchzeichenfolge ein und versuchen Sie, das Paging durchzuführen, um sicherzustellen, dass das Paging bei Sortier- und Filtervorgängen ordnungsgemäß funktioniert.
Gruppierung
In diesem Abschnitt wird eine Seite „About
“ (Info) erstellt, auf der angezeigt wird, wie viele Studenten sich für jedes Registrierungsdatum registriert haben. Bei dem Update wird eine Gruppierung verwendet, und es umfasst die folgenden Schritte:
- Erstellen Sie ein Ansichtsmodells für die auf der Seite
About
(Info) verwendeten Daten. - Aktualisieren Sie die
About
-Seite, um das Ansichtsmodell zu verwenden.
Erstellen des Ansichtsmodells
Erstellen Sie einen Ordner Models/SchoolViewModels.
Erstellen Sie SchoolViewModels/EnrollmentDateGroup.cs
mit dem folgenden Code:
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; }
}
}
Erstellen der Razor-Seite
Erstellen Sie eine Datei Pages/About.cshtml
mit dem folgenden Code:
@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>
Erstellen des Seitenmodells
Aktualisieren Sie die Datei Pages/About.cshtml.cs
mit dem folgenden Code:
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();
}
}
}
Die LINQ-Anweisung gruppiert die Studentenentitäten nach Anmeldedatum, berechnet die Anzahl der Entitäten in jeder Gruppe und speichert die Ergebnisse in einer Sammlung von EnrollmentDateGroup
-Ansichtsmodellobjekten.
Führen Sie die App aus, und navigieren Sie zur Seite „Info“. Die Anzahl der Studenten für die jeweiligen Anmeldedatumswerte wird in einer Tabelle angezeigt.
Nächste Schritte
Im nächsten Tutorial verwendet die App Migrationen zum Aktualisieren des Datenmodells.
In diesem Tutorial werden die Funktionen zum Sortieren, Filtern, Gruppieren und Paging hinzugefügt.
Die folgende Abbildung zeigt eine fertige Seite. Die Spaltenüberschriften sind Links, die zum Sortieren der Spalte angeklickt werden können. Durch wiederholtes Klicken auf eine Spaltenüberschrift wird zwischen aufsteigender und absteigender Sortierreihenfolge gewechselt.
Wenn nicht zu lösende Probleme auftreten, laden Sie die fertige App herunter.
Hinzufügen einer Sortierung zur Indexseite
Fügen Sie dem Students/Index.cshtml.cs
PageModel
Zeichenfolgen hinzu, damit die Sortierungsparameter darin enthalten sind:
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; }
Aktualisieren Sie Students/Index.cshtml.cs
OnGetAsync
mit dem folgenden Code:
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();
}
Der vorangehende Code erhält den Parameter sortOrder
aus der Abfragezeichenfolge in der URL. Die URL (einschließlich der Abfragezeichenfolge) wird vom Anchor-Taghilfsprogramm generiert.
Der Parameter sortOrder
entspricht entweder „Name“ oder „Datum“. Dem Parameter sortOrder
folgt optional „_desc“ zur Angabe einer absteigenden Reihenfolge. Standardmäßig wird eine aufsteigende Sortierreihenfolge verwendet.
Wenn die Indexseite über den Link Studenten angefordert wird, gibt es keine Abfragezeichenfolge. Die Studenten werden in aufsteigender Reihenfolge nach Nachnamen angezeigt. Die aufsteigende Reihenfolge nach Nachnamen (FallThrough-Fall) ist in der switch
-Anweisung die Standardeinstellung. Wenn der Benutzer auf einen Link in einer Spaltenüberschrift klickt, wird der entsprechende sortOrder
-Wert im Abfragezeichenfolgenwert bereitgestellt.
NameSort
und DateSort
werden auf der Razor-Seite verwendet, um die Hyperlinks in den Spaltenüberschriften mit den entsprechenden Abfragezeichenfolgenwerten zu konfigurieren:
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();
}
Der folgende Code enthält den bedingten C#-Operator „?:“:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
Die erste Zeile gibt an, dass wenn sortOrder
NULL oder leer ist, NameSort
auf „name_desc“ festgelegt ist. Wenn sortOrder
nicht NULL oder leer ist, wird NameSort
auf eine leere Zeichenfolge festgelegt.
?: operator
ist auch als ternärer Operator bekannt.
Durch diese beiden Anweisungen können auf der Seite die Hyperlinks in den Spaltenüberschriften wie folgt festgelegt werden:
Aktuelle Sortierreihenfolge | Hyperlink „Nachname“ | Hyperlink „Datum“ |
---|---|---|
Nachname (aufsteigend) | descending | ascending |
Nachname (absteigend) | ascending | ascending |
Datum (aufsteigend) | ascending | descending |
Datum (absteigend) | ascending | ascending |
Die Methode gibt über LINQ to Entities die Spalte an, nach der sortiert werden soll. Der Code initialisiert das Objekt IQueryable<Student>
vor der Switch-Anweisung und ändert es in die Switch-Anweisung:
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();
}
Wenn ein Objekt vom Typ IQueryable
erstellt oder geändert wird, wird keine Abfrage an die Datenbank gesendet. Die Abfrage wird erst dann ausgeführt, wenn das Objekt IQueryable
in eine Sammlung konvertiert wurde. IQueryable
werden in eine Sammlung konvertiert, indem eine Methode wie z.B. ToListAsync
aufgerufen wird. Aus diesem Grund führt der IQueryable
-Code zu einer einzelnen Abfrage, die bis zu folgender Anweisung nicht ausgeführt wird:
Student = await studentIQ.AsNoTracking().ToListAsync();
OnGetAsync
könnte mit einer Vielzahl von sortierbaren Spalten ausführlich werden.
Hinzufügen von Hyperlinks auf Spaltenüberschriften zur Studentenindexseite
Ersetzen Sie den Code in Students/Index.cshtml
durch folgenden hervorgehobenen Code:
@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>
Der vorangehende Code:
- Fügt Hyperlinks zu den Spaltenüberschriften
LastName
undEnrollmentDate
hinzu. - Verwendet die Informationen in
NameSort
undDateSort
zum Einrichten von Hyperlinks mit den Werten der aktuellen Sortierreihenfolge.
So überprüfen Sie die Funktionsfähigkeit der Sortierung:
- Führen Sie die App aus, und klicken Sie auf die Registerkarte Studenten.
- Klicken Sie auf Nachname.
- Klicken Sie auf Anmeldedatum.
So erhalten Sie ein besseres Verständnis des Codes:
- Legen Sie in
Students/Index.cshtml.cs
einen Breakpoint beiswitch (sortOrder)
fest. - Fügen Sie für
NameSort
undDateSort
ein Überwachungselement hinzu. - Legen Sie in
Students/Index.cshtml
einen Breakpoint bei@Html.DisplayNameFor(model => model.Student[0].LastName)
fest.
Führen Sie den Debugger schrittweise aus.
Hinzufügen eines Suchfelds zur Studentenindexseite
So fügen Sie Filter zur Indexseite „Studenten“ hinzu:
- Ein Textfeld und die Schaltfläche „Senden“ werden zur Razor Page hinzugefügt. Im Textfeld wird eine Suchzeichenfolge für den Vor- oder Nachnamen bereitgestellt.
- Das Seitenmodell wird aktualisiert, damit der Wert im Textfeld verwendet wird.
Hinzufügen der Filterfunktion zur Indexmethode
Aktualisieren Sie Students/Index.cshtml.cs
OnGetAsync
mit dem folgenden Code:
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();
}
Der vorangehende Code:
- Fügt den Parameter
searchString
zur MethodeOnGetAsync
hinzu. Der Suchzeichenfolgenwert wird aus einem Textfeld empfangen, das im nächsten Abschnitt hinzugefügt wird. - Hat eine
Where
-Klausel zur LINQ-Anweisung hinzugefügt. In derWhere
-Klausel werden nur die Studenten ausgewählt, deren Vor- oder Nachname die Suchzeichenfolge enthält. Die LINQ-Anweisung wird nur dann ausgeführt, wenn ein Wert vorhanden ist, nach dem gesucht werden soll.
Hinweis: Der vorangehende Code ruft die Methode Where
in einem IQueryable
-Objekt auf, und der Filter wird auf dem Server verarbeitet. In einigen Szenarios ruft die App möglicherweise die Methode Where
als Erweiterungsmethode für eine Auflistung im Speicher auf. Angenommen, _context.Students
wird beispielsweise von EF CoreDbSet
in eine Repositorymethode geändert, die eine IEnumerable
-Sammlung zurückgibt. Das Ergebnis wäre normalerweise identisch, aber in einigen Fällen kann es unterschiedlich ausfallen.
Bei der .NET Framework-Implementierung von Contains
wird beispielsweise standardmäßig ein Vergleich unter Beachtung der Groß-/Kleinschreibung vorgenommen. In SQL Server wird die Unterscheidung von Groß-/Kleinschreibung bei Contains
durch die Einstellung für die Sortierung der SQL Server-Instanz bestimmt. Bei SQL Server wird die Groß-/Kleinschreibung standardmäßig nicht beachtet. ToUpper
könnte aufgerufen werden, wenn die Groß-/Kleinschreibung bei der Durchführung des Tests explizit nicht beachtet werden soll:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
Der vorangehende Code würde sicherstellen, dass die Groß-/Kleinschreibung bei Ergebnissen nicht beachtet wird, wenn der Code so geändert wird, dass er IEnumerable
verwendet. Wenn Contains
in einer IEnumerable
-Sammlung verwendet wird, wird die .NET Core-Implementierung verwendet. Wenn Contains
in einem IQueryable
-Objekt aufgerufen wird, wird die Datenbankimplementierung verwendet. Die Rückgabe eines Objekts vom Typ IEnumerable
aus einem Repository kann signifikante Leistungseinbußen mit sich bringen:
- Alle Zeilen werden vom Datenbankserver zurückgegeben.
- Der Filter wird auf alle zurückgegebenen Zeilen in der Anwendung angewendet.
Beim Aufrufen von ToUpper
kommt es zu Leistungseinbußen. Der ToUpper
-Code in der WHERE-Klausel der TSQL SELECT-Anweisung eine Funktion hinzu. Durch die hinzugefügte Funktion wird verhindert, dass der Optimierer einen Index verwendet. Da in SQL die Groß-/Kleinschreibung beachtet wird, sollte ToUpper
möglichst nicht aufgerufen werden, wenn dies nicht notwendig ist.
Hinzufügen eines Suchfelds zur Studentenindexseite
Fügen Sie folgenden hervorgehobenen Code zu Pages/Students/Index.cshtml
hinzu, um die Schaltfläche Suchen und sortiertes Chrom zu erstellen.
@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">
Der vorangehende Code verwendet das <form>
-Taghilfsprogramm, um das Suchtextfeld und die Schaltfläche hinzuzufügen. Das <form>
-Taghilfsprogramm sendet Formulardaten standardmäßig mit einer POST-Anforderung. Mit der POST-Anforderung werden die Parameter im HTTP-Nachrichtentext und nicht in der URL übergeben. Bei Verwendung einer HTTP GET-Anforderung werden die Formulardaten als Abfragezeichenfolgen in der URL übergeben. Durch die Übergabe der Daten mit Abfragezeichenfolgen können Benutzer die URL als Lesezeichen speichern. In den W3C-Richtlinien wird die Verwendung einer GET-Anforderung empfohlen, wenn die Aktion nicht zu einem Update führt.
Testen der App:
- Klicken Sie auf die Registerkarte Studenten, und geben Sie eine Suchzeichenfolge ein.
- Klicken Sie auf Suchen.
Beachten Sie, dass die URL die Suchzeichenfolge enthält.
http://localhost:5000/Students?SearchString=an
Wenn die Seite als Lesezeichen gespeichert wird, enthält das Lesezeichen die URL der Seite und die SearchString
-Abfragezeichenfolge. Durch method="get"
im form
-Tag wurde die Abfragezeichenfolge generiert.
Aktuell geht der Filterwert aus dem Feld Suchen verloren, wenn ein Link zur Sortierung der Spaltenüberschrift ausgewählt wird. Das Problem des verlorenen Filterwerts wird im nächsten Abschnitt behoben.
Hinzufügen von Pagingfunktionen der Studentenindexseite
In diesem Abschnitt wird eine PaginatedList
-Klasse zur Unterstützung von Paging erstellt. Die PaginatedList
-Klasse verwendet Skip
- und Take
-Anweisungen zum Filtern von Daten auf dem Server, statt alle Zeilen der Tabelle abzurufen. Die folgende Abbildung zeigt die Pagingschaltflächen.
Erstellen Sie im Projektordner PaginatedList.cs
mit folgendem Code:
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);
}
}
}
Die Methode CreateAsync
im vorangehenden Code übernimmt die Seitengröße und die Seitenzahl und wendet die entsprechenden Skip
- und Take
-Anweisungen auf das Objekt IQueryable
an. Wenn ToListAsync
im Objekt IQueryable
aufgerufen wird, wird eine Liste zurückgegeben, die nur die angeforderte Seite enthält. Die Eigenschaften HasPreviousPage
und HasNextPage
dienen zum Aktivieren oder Deaktivieren der Schaltflächen Zurück und Weiter für das Paging.
Die Methode CreateAsync
dient zum Erstellen des Objekts PaginatedList<T>
. Ein Konstruktor kann das Objekt PaginatedList<T>
nicht erstellen. Konstruktoren können keinen asynchronen Code ausführen.
Fügen Sie Pagingfunktionen zur Indexmethode hinzu
Aktualisieren Sie in Students/Index.cshtml.cs
den Typ von Student
von IList<Student>
in PaginatedList<Student>
:
public PaginatedList<Student> Student { get; set; }
Aktualisieren Sie Students/Index.cshtml.cs
OnGetAsync
mit dem folgenden Code:
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);
}
Der vorangehende Code fügt den Seitenindex, die aktuelle sortOrder
-Eigenschaft und die currentFilter
-Eigenschaft zur Methodensignatur hinzu.
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
Alle Parameter sind NULL, wenn:
- Die Seite über den Link Studenten aufgerufen wird.
- Der Benutzer auf keinen Link für das Paging oder die Sortierung geklickt hat.
Wenn auf einen Paging-Link geklickt wird, enthält die Seitenindexvariable die anzuzeigende Seitenzahl.
CurrentSort
stellt die Razor Page mit der aktuellen Sortierreihenfolge bereit. Die aktuelle Sortierreihenfolge muss in den Paging-Links enthalten sein, damit die Sortierreihenfolge während des Pagings beibehalten wird.
CurrentFilter
stellt die Razor Page mit der aktuellen Filterzeichenfolge bereit. Der CurrentFilter
-Wert:
- Muss in den Paging-Links enthalten sein, damit die Filtereinstellungen während des Pagings beibehalten werden.
- Muss im Textfeld wiederhergestellt werden, wenn die Seite erneut angezeigt wird.
Wenn die Suchzeichenfolge während des Pagings geändert wird, wird die Seite auf 1 zurückgesetzt. Die Seite muss auf 1 zurückgesetzt werden, da der neue Filter andere Daten anzeigen kann. Folgendes gilt, wenn ein Suchwert eingegeben und auf Senden geklickt wird:
- Die Suchzeichenfolge wird geändert.
- Der Parameter
searchString
ist nicht NULL.
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
Die Methode PaginatedList.CreateAsync
konvertiert die Studentenabfrage in eine einzelne Studentenseite in einem Sammlungstyp, der das Paging unterstützt. Diese einzelne Studentenseite wird an die Razor Page übergeben.
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
Die zwei Fragezeichen in PaginatedList.CreateAsync
stellen den NULL-Sammeloperator dar. Der NULL-Sammeloperator definiert einen Standardwert für einen auf NULL festlegbaren Typ. Der Ausdruck (pageIndex ?? 1)
bedeutet, dass der Wert von pageIndex
zurückgegeben wird, wenn dieser einen Wert aufweist. Wenn der pageIndex
keinen Wert aufweist, wird 1 zurückgegeben.
Hinzufügen von Paging-Links zur Razor Page für Studenten
Aktualisieren Sie das Markup in Students/Index.cshtml
. Die Änderungen werden hervorgehoben:
@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>
In den Links der Spaltenüberschriften wird die Abfragezeichenfolge verwendet, um die aktuelle Suchzeichenfolge an die Methode OnGetAsync
zu übergeben, damit der Benutzer Filterergebnisse sortieren kann:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
Die Pagingschaltflächen werden durch Taghilfsprogramme angezeigt:
<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>
Führen Sie die App aus, und navigieren Sie zur Studentenseite.
- Klicken Sie in verschiedenen Sortierreihenfolgen auf die Paging-Links, um sicherzustellen, dass das Paging funktioniert.
- Geben Sie eine Suchzeichenfolge ein und versuchen Sie, das Paging durchzuführen, um sicherzustellen, dass das Paging bei Sortier- und Filtervorgängen ordnungsgemäß funktioniert.
So erhalten Sie ein besseres Verständnis des Codes:
- Legen Sie in
Students/Index.cshtml.cs
einen Breakpoint beiswitch (sortOrder)
fest. - Fügen Sie für
NameSort
,DateSort
,CurrentSort
undModel.Student.PageIndex
ein Überwachungselement hinzu. - Legen Sie in
Students/Index.cshtml
einen Breakpoint bei@Html.DisplayNameFor(model => model.Student[0].LastName)
fest.
Führen Sie den Debugger schrittweise aus.
Aktualisieren der Info-Seite zum Anzeigen von Studentenstatistiken
In diesem Schritt wird Pages/About.cshtml
aktualisiert, um anzuzeigen, wie viele Studenten sich am jeweiligen Anmeldedatum angemeldet haben. Bei dem Update wird eine Gruppierung verwendet, und es umfasst die folgenden Schritte:
- Das Erstellen eines Ansichtsmodells für die auf der Info-Seite verwendeten Daten.
- Das Aktualisieren der Info-Seite, um das Ansichtsmodell zu verwenden.
Erstellen des Ansichtsmodells
Erstellen Sie im Ordner Models (Modelle) den Ordner SchoolViewModels.
Fügen Sie im Ordner SchoolViewModels die Datei EnrollmentDateGroup.cs
mit folgendem Code hinzu:
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; }
}
}
Aktualisieren des Info-Seitenmodells
Die Webvorlagen in ASP.NET Core 2.2 enthalten nicht die Seite „Info“. Erstellen Sie bei Verwenden von ASP.NET Core 2.2 die „Informationen zu Razor Page“.
Aktualisieren Sie die Datei Pages/About.cshtml.cs
mit dem folgenden Code:
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();
}
}
}
Die LINQ-Anweisung gruppiert die Studentenentitäten nach Anmeldedatum, berechnet die Anzahl der Entitäten in jeder Gruppe und speichert die Ergebnisse in einer Sammlung von EnrollmentDateGroup
-Ansichtsmodellobjekten.
Ändern der Seite „Informationen zu Razor Page“
Ersetzen Sie den Code in der Datei Pages/About.cshtml
durch folgenden Code:
@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>
Führen Sie die App aus, und navigieren Sie zur Seite „Info“. Die Anzahl der Studenten für die jeweiligen Anmeldedatumswerte wird in einer Tabelle angezeigt.
Wenn nicht zu lösende Probleme auftreten, laden Sie die abgeschlossene Anwendung für diese Phase herunter.
Zusätzliche Ressourcen
Im nächsten Tutorial verwendet die App Migrationen zum Aktualisieren des Datenmodells.
ASP.NET Core
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Tickets als Feedbackmechanismus für Inhalte auslaufen lassen und es durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unter:Einreichen und Feedback anzeigen für