Partie 3, Razor Pages avec EF Core dans ASP.NET Core - Tri, Filtrage, Pagination
Par Tom Dykstra, Jeremy Likness et Jon P Smith
L’application web Contoso University montre comment créer des applications web Pages Razor avec EF Core et Visual Studio. Pour obtenir des informations sur la série de didacticiels, consultez le premier didacticiel.
Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez l’application finale et comparez ce code à celui que vous avez créé en suivant le tutoriel.
Ce tutoriel ajoute des fonctionnalités de tri, de filtrage et de pagination aux pages des étudiants.
L’illustration suivante présente une page complète. Les en-têtes de colonne sont des liens hypertexte permettant de trier la colonne. Cliquez de façon répétée sur un en-tête de colonne pour changer l’ordre de tri (croissant ou décroissant).
Ajouter la fonctionnalité de tri
Remplacez le code de Pages/Students/Index.cshtml.cs
par le code suivant pour ajouter le tri.
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();
}
}
Le code précédent :
- Nécessite l’ajout de
using System;
. - Ajoute des propriétés devant contenir les paramètres de tri.
- Remplace le nom de la propriété
Student
parStudents
. - Remplace le code de la méthode
OnGetAsync
.
La méthode OnGetAsync
reçoit un paramètre sortOrder
à partir de la chaîne de requête dans l’URL. L’URL et la chaîne de requête sont générées par le Tag Helper d’ancre.
Le paramètre sortOrder
est Name
ou Date
. Le paramètre sortOrder
peut être suivi de _desc
pour spécifier l’ordre décroissant. L’ordre de tri par défaut est croissant.
Quand la page Index est demandée à partir du lien Students, il n’existe aucune chaîne de requête. Les étudiants sont affichés par nom de famille dans l’ordre croissant. L’ordre croissant par nom est le default
dans l’instruction switch
. Quand l’utilisateur clique sur un lien d’en-tête de colonne, la valeur sortOrder
appropriée est fournie dans la valeur de chaîne de requête.
NameSort
et DateSort
sont utilisés par la page Razor pour configurer les liens hypertexte d’en-tête de colonne avec les valeurs de chaîne de requête appropriées :
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
Le code utilise l’opérateur conditionnel C# ?:. L’opérateur ?:
est un opérateur ternaire ; il prend trois opérandes. La première ligne indique que quand sortOrder
est null ou vide, NameSort
prend la valeur name_desc
. Si sortOrder
n’est pas null ou vide, NameSort
prend pour valeur une chaîne vide.
Ces deux instructions permettent à la page de définir les liens hypertexte d’en-tête de colonne comme suit :
Ordre de tri actuel | Lien hypertexte Nom de famille | Lien hypertexte Date |
---|---|---|
Nom de famille croissant | descending | ascending |
Nom de famille décroissant | ascending | ascending |
Date croissante | ascending | descending |
Date décroissante | ascending | ascending |
La méthode utilise LINQ to Entities pour spécifier la colonne d’après laquelle effectuer le tri. Le code initialise un IQueryable<Student>
avant l’instruction switch, et le modifie dans l’instruction 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();
Quand un IQueryable
est créé ou modifié, aucune requête n’est envoyée à la base de données. La requête n’est pas exécutée tant que l’objet IQueryable
n’a pas été converti en collection. Les IQueryable
sont convertis en collection en appelant une méthode telle que ToListAsync
. Ainsi, le code IQueryable
génère une requête unique qui n’est pas exécutée avant l’instruction suivante :
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync
peut contenir un grand nombre de colonnes triables. Pour connaître les autres méthodes permettant de coder cette fonctionnalité, consultez Utiliser du code dynamique LINQ pour simplifier le code dans la version MVC de cette série de tutoriels.
Ajouter des liens hypertexte d’en-tête de colonne à la page d’index des étudiants
Remplacez le code dans Students/Index.cshtml
par le code suivant. Les modifications sont mises en surbrillance.
@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>
Le code précédent :
- Ajoute des liens hypertexte aux en-têtes de colonne
LastName
etEnrollmentDate
. - Utilise les informations contenues dans
NameSort
etDateSort
pour définir des liens hypertexte avec les valeurs d’ordre de tri actuelles. - Remplace l’en-tête Index de la page par l’en-tête Students.
- Remplace
Model.Student
parModel.Students
.
Pour vérifier que le tri fonctionne
- Exécutez l’application et sélectionnez l’onglet Students.
- Cliquez sur les en-têtes de colonne.
Ajouter la fonctionnalité de filtrage
Pour ajouter le filtrage à la page d’index des étudiants :
- Une zone de texte et un bouton d’envoi sont ajoutés à la page Razor. La zone de texte fournit une chaîne de recherche sur le prénom ou le nom de famille.
- Le modèle de page est mis à jour pour utiliser la valeur de zone de texte.
Mettre à jour la méthode OnGetAsync
Remplacez le code de Students/Index.cshtml.cs
par le code suivant pour ajouter le filtrage :
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();
}
}
Le code précédent :
- Ajoute le paramètre
searchString
à la méthodeOnGetAsync
, et enregistre la valeur de paramètre dans la propriétéCurrentFilter
. La valeur de chaîne de recherche est reçue à partir d’une zone de texte qui est ajoutée dans la section suivante. - Ajoute une clause
Where
à l’instruction LINQ. La clauseWhere
sélectionne uniquement les étudiants dont le prénom ou le nom de famille contient la chaîne de recherche. L’instruction LINQ est exécutée uniquement s’il y a une valeur à rechercher.
IQueryable et IEnumerable
Le code appelle la méthode Where de l’objet IQueryable
, et le filtre est traité sur le serveur. Dans certains scénarios, l’application peut appeler la méthode Where
en tant que méthode d’extension sur une collection en mémoire. Par exemple, supposez que _context.Students
passe de EF CoreDbSet
à une méthode de référentiel qui retourne une collection IEnumerable
. Le résultat serait normalement le même, mais dans certains cas il peut être différent.
Par exemple, l’implémentation .NET Framework de Contains
effectue par défaut une comparaison respectant la casse. Dans SQL Server, le respect de la casse de Contains
est déterminé par le paramètre de classement de l’instance de SQL Server. Par défaut, SQL Server ne respecte pas la casse. Par défaut, SQLite est sensible à la casse. ToUpper
peut être appelée pour que le test ne respecte pas la casse de manière explicite :
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
Le code précédent garantit que le filtre n’est pas sensible à la casse, même si la méthode Where
est appelée sur un IEnumerable
ou s’exécute sur SQLite.
Quand Contains
est appelée sur une collection IEnumerable
, l’implémentation .NET Core est utilisée. Quand Contains
est appelée sur un objet IQueryable
, l’implémentation de base de données est utilisée.
Pour des raisons de performances, il est généralement préférable d’appeler Contains
sur un IQueryable
. Avec IQueryable
, le filtrage est effectué par le serveur de base de données. Si un IEnumerable
est créé en premier, toutes les lignes doivent être retournées à partir du serveur de base de données.
Il existe un coût en matière de performances en cas d’appel à ToUpper
. Le code ToUpper
ajoute une fonction dans la clause WHERE de l’instruction TSQL SELECT. La fonction ajoutée empêche l’optimiseur d’utiliser un index. Étant donné que SQL est installé sans respect de la casse, il est préférable d’éviter l’appel à ToUpper
quand il n’est pas nécessaire.
Pour plus d’informations, consultez How to use case-insensitive query with Sqlite provider.
Mettre à jour la page Razor
Remplacez le code dans Pages/Students/Index.cshtml
pour ajouter un bouton Rechercher.
@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>
Le code précédent utilise le Tag Helper<form>
pour ajouter le bouton et la zone de texte de recherche. Par défaut, le Tag Helper <form>
envoie les données de formulaire avec un POST. Avec POST, les paramètres sont passés dans le corps du message HTTP et non dans l’URL. Quand HTTP GET est utilisé, les données du formulaire sont transmises dans l’URL sous forme de chaînes de requête. La transmission des données avec des chaînes de requête permet aux utilisateurs d’ajouter l’URL aux favoris. Les recommandations du W3C stipulent que GET doit être utilisé quand l’action ne produit pas de mise à jour.
Testez l’application :
Sélectionnez l’onglet Students et entrez une chaîne de recherche. Si vous utilisez SQLite, le filtre n’est pas sensible à la casse seulement si vous avez implémenté le code
ToUpper
facultatif indiqué plus haut.Sélectionnez Recherche.
Notez que l’URL contient la chaîne de recherche. Par exemple :
https://localhost:5001/Students?SearchString=an
Si la page est dans les favoris, le favori contient l’URL de la page et la chaîne de requête SearchString
. method="get"
dans la balise form
est ce qui a provoqué la génération de la chaîne de requête.
Actuellement, quand un lien de tri d’en-tête de colonne est sélectionné, la valeur du filtre de la zone Search est perdue. La valeur de filtre perdue est corrigée dans la section suivante.
Ajouter la fonctionnalité de pagination
Dans cette section, nous allons créer une classe PaginatedList
pour prendre en charge la pagination. La classe PaginatedList
utilise des instructions Skip
et Take
pour filtrer les données sur le serveur au lieu de récupérer toutes les lignes de la table. L’illustration suivante montre les boutons de pagination.
Créer la classe PaginatedList
Dans le dossier du projet, créez PaginatedList.cs
avec le code suivant :
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);
}
}
}
La méthode CreateAsync
dans le code précédent prend la taille de page et le numéro de page, et applique les instructions Skip
et Take
appropriées au IQueryable
. Quand ToListAsync
est appelée sur le IQueryable
, elle retourne une liste contenant uniquement la page demandée. Les propriétés HasPreviousPage
et HasNextPage
sont utilisées pour activer ou désactiver les boutons de pagination Previous et Next.
La méthode CreateAsync
est utilisée pour créer le PaginatedList<T>
. Un constructeur ne peut pas créer l’objet PaginatedList<T>
, car les constructeurs ne peuvent pas exécuter du code asynchrone.
Ajouter la taille de page à la configuration
Ajoutez PageSize
au fichier de appsettings.json
Configuration :
{
"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"
}
}
Ajouter la pagination à IndexModel
Remplacez le code dans Students/Index.cshtml.cs
pour ajouter la pagination.
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);
}
}
}
Le code précédent :
- Remplace le type
IList<Student>
de la propriétéStudents
par le typePaginatedList<Student>
. - Ajoute l’index de page, le
sortOrder
actuel et lecurrentFilter
à la signature de méthodeOnGetAsync
. - Enregistre l’ordre de tri dans la propriété
CurrentSort
. - Rétablit la valeur 1 pour l’index de la page lorsqu’il existe une nouvelle chaîne de recherche.
- Utilise la classe
PaginatedList
pour accéder aux entités d’étudiants. - Définit
pageSize
sur 3 à partir de Configuration, 4 si la configuration échoue.
Tous les paramètres reçus par OnGetAsync
sont Null si :
- La page est appelée à partir du lien Students.
- L’utilisateur n’a pas cliqué sur un lien de pagination ou de tri.
Quand l’utilisateur clique sur un lien de pagination, la variable d’index de page contient le numéro de page à afficher.
La propriété CurrentSort
fournit l’ordre de tri actuel à la page Razor. L’ordre de tri actuel doit être inclus dans les liens de pagination afin de conserver l’ordre de tri lors de la pagination.
La propriété CurrentFilter
fournit la chaîne de filtrage actuelle à la page Razor. La valeur CurrentFilter
:
- Doit être incluse dans les liens de pagination afin de conserver les paramètres de filtre lors de la pagination.
- Doit être restaurée à la zone de texte quand la page est réaffichée.
Si la chaîne de recherche est modifiée pendant la pagination, la page est réinitialisée à 1. La page doit être réinitialisée à 1, car le nouveau filtre peut entraîner l’affichage de données différentes. Quand une valeur de recherche est entrée et que le bouton Submit est sélectionné :
- La chaîne de recherche est changée.
- Le paramètre
searchString
n’est pas null.
La méthode PaginatedList.CreateAsync
convertit la requête d’étudiant en une seule page d’étudiants dans un type de collection qui prend en charge la pagination. Cette page unique d’étudiants est passée à la page Razor.
Les deux points d’interrogation situés après pageIndex
dans l’appel PaginatedList.CreateAsync
représentent l’opérateur de fusion avec valeur Null. L’opérateur de fusion de Null définit une valeur par défaut pour un type nullable. L’expression pageIndex ?? 1
retourne la valeur pageIndex
si elle a une valeur ; sinon, elle retourne 1.
Ajouter des liens de pagination
Remplacez le code dans Students/Index.cshtml
par le code suivant. Les modifications apparaissent en surbrillance :
@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>
Les liens d’en-tête de colonne utilisent la chaîne de requête pour passer la chaîne de recherche actuelle à la méthode OnGetAsync
:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
Les boutons de changement de page sont affichés par des Tag Helpers :
<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>
Exécutez l’application et accédez à la page des étudiants.
- Pour vérifier que la pagination fonctionne, cliquez sur les liens de pagination dans différents ordres de tri.
- Pour vérifier que la pagination fonctionne correctement avec le tri et le filtrage, entrez une chaîne de recherche et essayez de changer de page.
Regroupement
Cette section crée la page About
(À propos) qui indique le nombre d’étudiants inscrits pour chaque date d’inscription. La mise à jour utilise le regroupement et comprend les étapes suivantes :
- Créer un modèle de vue pour les données utilisées par la page
About
. - Mettre à jour la page
About
pour utiliser le modèle de vue.
Créer le modèle d’affichage
Créez un dossier Models/SchoolViewModels.
Créez SchoolViewModels/EnrollmentDateGroup.cs
avec le code suivant :
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; }
}
}
Créer la page Razor
Créez un fichier Pages/About.cshtml
avec le code suivant :
@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>
Créer le modèle de page
Mettez à jour le fichier Pages/About.cshtml.cs
avec le code suivant :
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();
}
}
}
L’instruction LINQ regroupe les entités Student par date d’inscription, calcule le nombre d’entités dans chaque groupe et stocke les résultats dans une collection d’objets de modèle de vue EnrollmentDateGroup
.
Exécutez l’application et accédez à la page About. Le nombre d’étudiants pour chaque date d’inscription s’affiche dans une table.
Étapes suivantes
Dans le didacticiel suivant, l’application utilise des migrations pour mettre à jour le modèle de données.
Dans ce didacticiel, nous allons ajouter des fonctionnalités de tri, de filtrage, de regroupement et de pagination.
L’illustration suivante présente une page complète. Les en-têtes de colonne sont des liens hypertexte permettant de trier la colonne. Un clic sur un en-tête de colonne permet de changer l’ordre de tri (croissant ou décroissant).
Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez l’application terminée.
Ajouter le tri à la page Index
Ajoutez des chaînes au Students/Index.cshtml.cs
PageModel
pour contenir les paramètres de tri :
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; }
Mettez à jour la méthode Students/Index.cshtml.cs
OnGetAsync
avec le code suivant :
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();
}
Le code précédent reçoit un paramètre sortOrder
à partir de la chaîne de requête dans l’URL. L’URL (y compris la chaîne de requête) est générée par le Tag Helper d’ancre.
Le paramètre sortOrder
est « Name » ou « Date ». Le paramètre sortOrder
est éventuellement suivi de « _desc » pour spécifier l’ordre décroissant. L’ordre de tri par défaut est croissant.
Quand la page Index est demandée à partir du lien Students, il n’existe aucune chaîne de requête. Les étudiants sont affichés par nom de famille dans l’ordre croissant. Le tri croissant par nom de famille est la valeur par défaut dans l’instruction switch
. Quand l’utilisateur clique sur un lien d’en-tête de colonne, la valeur sortOrder
appropriée est fournie dans la valeur de chaîne de requête.
NameSort
et DateSort
sont utilisés par la page Razor pour configurer les liens hypertexte d’en-tête de colonne avec les valeurs de chaîne de requête appropriées :
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();
}
Le code suivant contient l’opérateur ?: conditionnel C# :
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
La première ligne spécifie que lorsque sortOrder
est null ou vide, NameSort
a la valeur « name_desc ». Si sortOrder
n’est pas null ou vide, NameSort
est défini sur une chaîne vide.
?: operator
est également appelé opérateur ternaire.
Ces deux instructions permettent à la page de définir les liens hypertexte d’en-tête de colonne comme suit :
Ordre de tri actuel | Lien hypertexte Nom de famille | Lien hypertexte Date |
---|---|---|
Nom de famille croissant | descending | ascending |
Nom de famille décroissant | ascending | ascending |
Date croissante | ascending | descending |
Date décroissante | ascending | ascending |
La méthode utilise LINQ to Entities pour spécifier la colonne d’après laquelle effectuer le tri. Le code initialise un IQueryable<Student>
avant l’instruction switch, et le modifie dans l’instruction 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();
}
Quand un IQueryable
est créé ou modifié, aucune requête n’est envoyée à la base de données. La requête n’est pas exécutée tant que l’objet IQueryable
n’a pas été converti en collection. Les IQueryable
sont convertis en collection en appelant une méthode telle que ToListAsync
. Ainsi, le code IQueryable
génère une requête unique qui n’est pas exécutée avant l’instruction suivante :
Student = await studentIQ.AsNoTracking().ToListAsync();
OnGetAsync
peut contenir un grand nombre de colonnes triables.
Ajouter des liens hypertexte d’en-tête de colonne à la page d’index des étudiants
Remplacez le code de Students/Index.cshtml
par le code mis en évidence suivant :
@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>
Le code précédent :
- Ajoute des liens hypertexte aux en-têtes de colonne
LastName
etEnrollmentDate
. - Utilise les informations contenues dans
NameSort
etDateSort
pour définir des liens hypertexte avec les valeurs d’ordre de tri actuelles.
Pour vérifier que le tri fonctionne
- Exécutez l’application et sélectionnez l’onglet Students.
- Cliquez sur Last Name.
- Cliquez sur Enrollment Date.
Pour mieux comprendre le fonctionnement du code
- Dans
Students/Index.cshtml.cs
, définissez un point d’arrêt surswitch (sortOrder)
. - Ajoutez un espion pour
NameSort
etDateSort
. - Dans
Students/Index.cshtml
, définissez un point d’arrêt sur@Html.DisplayNameFor(model => model.Student[0].LastName)
.
Effectuez un pas à pas détaillé dans le débogueur.
Ajouter une zone de recherche à la page d’index des étudiants
Pour ajouter le filtrage à la page d’index des étudiants :
- Une zone de texte et un bouton d’envoi sont ajoutés à la page Razor. La zone de texte fournit une chaîne de recherche sur le prénom ou le nom de famille.
- Le modèle de page est mis à jour pour utiliser la valeur de zone de texte.
Ajouter la fonctionnalité de filtrage à la méthode Index
Mettez à jour la méthode Students/Index.cshtml.cs
OnGetAsync
avec le code suivant :
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();
}
Le code précédent :
- Ajoute le paramètre
searchString
à la méthodeOnGetAsync
. La valeur de chaîne de recherche est reçue à partir d’une zone de texte qui est ajoutée dans la section suivante. - A ajouté une clause
Where
à l’instruction LINQ. La clauseWhere
sélectionne uniquement les étudiants dont le prénom ou le nom de famille contient la chaîne de recherche. L’instruction LINQ est exécutée uniquement s’il y a une valeur à rechercher.
Remarque : Le code précédent appelle la méthode Where
sur un objet IQueryable
, et le filtre est traité sur le serveur. Dans certains scénarios, l’application peut appeler la méthode Where
en tant que méthode d’extension sur une collection en mémoire. Par exemple, supposez que _context.Students
passe de EF CoreDbSet
à une méthode de référentiel qui retourne une collection IEnumerable
. Le résultat serait normalement le même, mais dans certains cas il peut être différent.
Par exemple, l’implémentation .NET Framework de Contains
effectue par défaut une comparaison respectant la casse. Dans SQL Server, le respect de la casse de Contains
est déterminé par le paramètre de classement de l’instance de SQL Server. Par défaut, SQL Server ne respecte pas la casse. ToUpper
peut être appelée pour que le test ne respecte pas la casse de manière explicite :
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
Le code précédent garantit que les résultats ne respectent pas la casse si le code change et utilise IEnumerable
. Quand Contains
est appelée sur une collection IEnumerable
, l’implémentation .NET Core est utilisée. Quand Contains
est appelée sur un objet IQueryable
, l’implémentation de base de données est utilisée. Retourner un IEnumerable
à partir d’un référentiel peut entraîner une dégradation significative des performances :
- Toutes les lignes sont retournées à partir du serveur de base de données.
- Le filtre est appliqué à toutes les lignes retournées dans l’application.
Il existe un coût en matière de performances en cas d’appel à ToUpper
. Le code ToUpper
ajoute une fonction dans la clause WHERE de l’instruction TSQL SELECT. La fonction ajoutée empêche l’optimiseur d’utiliser un index. Étant donné que SQL est installé sans respect de la casse, il est préférable d’éviter l’appel à ToUpper
quand il n’est pas nécessaire.
Ajouter une zone de recherche à la page d’index des étudiants
Dans Pages/Students/Index.cshtml
, ajoutez le code en évidence suivant pour créer un bouton Search et le contrôle Chrome assorti.
@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">
Le code précédent utilise le Tag Helper<form>
pour ajouter le bouton et la zone de texte de recherche. Par défaut, le Tag Helper <form>
envoie les données de formulaire avec un POST. Avec POST, les paramètres sont passés dans le corps du message HTTP et non dans l’URL. Quand HTTP GET est utilisé, les données du formulaire sont transmises dans l’URL sous forme de chaînes de requête. La transmission des données avec des chaînes de requête permet aux utilisateurs d’ajouter l’URL aux favoris. Les recommandations du W3C stipulent que GET doit être utilisé quand l’action ne produit pas de mise à jour.
Testez l’application :
- Sélectionnez l’onglet Students et entrez une chaîne de recherche.
- Sélectionnez Recherche.
Notez que l’URL contient la chaîne de recherche.
http://localhost:5000/Students?SearchString=an
Si la page est dans les favoris, le favori contient l’URL de la page et la chaîne de requête SearchString
. method="get"
dans la balise form
est ce qui a provoqué la génération de la chaîne de requête.
Actuellement, quand un lien de tri d’en-tête de colonne est sélectionné, la valeur du filtre de la zone Search est perdue. La valeur de filtre perdue est corrigée dans la section suivante.
Ajouter la fonctionnalité de changement de page à la page d’index des étudiants
Dans cette section, nous allons créer une classe PaginatedList
pour prendre en charge la pagination. La classe PaginatedList
utilise des instructions Skip
et Take
pour filtrer les données sur le serveur au lieu de récupérer toutes les lignes de la table. L’illustration suivante montre les boutons de pagination.
Dans le dossier du projet, créez PaginatedList.cs
avec le code suivant :
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);
}
}
}
La méthode CreateAsync
dans le code précédent prend la taille de page et le numéro de page, et applique les instructions Skip
et Take
appropriées au IQueryable
. Quand ToListAsync
est appelée sur le IQueryable
, elle retourne une liste contenant uniquement la page demandée. Les propriétés HasPreviousPage
et HasNextPage
sont utilisées pour activer ou désactiver les boutons de pagination Previous et Next.
La méthode CreateAsync
est utilisée pour créer le PaginatedList<T>
. Un constructeur ne peut pas créer l’objet PaginatedList<T>
; les constructeurs ne peuvent pas exécuter du code asynchrone.
Ajouter la fonctionnalité de pagination à la méthode Index
Dans Students/Index.cshtml.cs
, mettez à jour le type de Student
de IList<Student>
à PaginatedList<Student>
:
public PaginatedList<Student> Student { get; set; }
Mettez à jour la méthode Students/Index.cshtml.cs
OnGetAsync
avec le code suivant :
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);
}
Le code précédent ajoute l’index de page, le sortOrder
actuel et le currentFilter
à la signature de méthode.
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
Tous les paramètres sont null quand :
- La page est appelée à partir du lien Students.
- L’utilisateur n’a pas cliqué sur un lien de pagination ou de tri.
Quand l’utilisateur clique sur un lien de pagination, la variable d’index de page contient le numéro de page à afficher.
CurrentSort
fournit l’ordre de tri actuel à la page Razor. L’ordre de tri actuel doit être inclus dans les liens de pagination afin de conserver l’ordre de tri lors de la pagination.
CurrentFilter
fournit la chaîne de filtre actuelle à la page Razor. La valeur CurrentFilter
:
- Doit être incluse dans les liens de pagination afin de conserver les paramètres de filtre lors de la pagination.
- Doit être restaurée à la zone de texte quand la page est réaffichée.
Si la chaîne de recherche est modifiée pendant la pagination, la page est réinitialisée à 1. La page doit être réinitialisée à 1, car le nouveau filtre peut entraîner l’affichage de données différentes. Quand une valeur de recherche est entrée et que le bouton Submit est sélectionné :
- La chaîne de recherche est changée.
- Le paramètre
searchString
n’est pas null.
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
La méthode PaginatedList.CreateAsync
convertit la requête d’étudiant en une seule page d’étudiants dans un type de collection qui prend en charge la pagination. Cette page unique d’étudiants est passée à la page Razor.
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
Les deux points d’interrogation dans PaginatedList.CreateAsync
représentent l’opérateur de fusion de Null. L’opérateur de fusion de Null définit une valeur par défaut pour un type nullable. L’expression (pageIndex ?? 1)
signifie qu’il faut retourner la valeur de pageIndex
s’il a une valeur. Si pageIndex
n’a pas de valeur, il faut retourner 1.
Ajouter des liens de pagination à la page Razor d’étudiant
Mettez à jour le balisage dans Students/Index.cshtml
. Les modifications apparaissent en surbrillance :
@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>
Les liens d’en-tête de colonne utilisent la chaîne de requête pour passer la chaîne de recherche actuelle à la méthode OnGetAsync
afin que l’utilisateur puisse trier dans les résultats du filtrage :
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
Les boutons de changement de page sont affichés par des Tag Helpers :
<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>
Exécutez l’application et accédez à la page des étudiants.
- Pour vérifier que la pagination fonctionne, cliquez sur les liens de pagination dans différents ordres de tri.
- Pour vérifier que la pagination fonctionne correctement avec le tri et le filtrage, entrez une chaîne de recherche et essayez de changer de page.
Pour mieux comprendre le fonctionnement du code
- Dans
Students/Index.cshtml.cs
, définissez un point d’arrêt surswitch (sortOrder)
. - Ajoutez un espion pour
NameSort
,DateSort
,CurrentSort
etModel.Student.PageIndex
. - Dans
Students/Index.cshtml
, définissez un point d’arrêt sur@Html.DisplayNameFor(model => model.Student[0].LastName)
.
Effectuez un pas à pas détaillé dans le débogueur.
Mettez à jour la page About pour afficher les statistiques sur les étudiants
Lors de cette étape, nous allons mettre à jour Pages/About.cshtml
afin d’afficher le nombre d’étudiants qui se sont inscrits pour chaque date d’inscription. La mise à jour utilise le regroupement et comprend les étapes suivantes :
- Créer un modèle de vue pour les données utilisées par la page About.
- Mettre à jour la page About pour utiliser le modèle de vue.
Créer le modèle d’affichage
Créez un dossier SchoolViewModels dans le dossier Models.
Dans le dossier SchoolViewModels, ajoutez un EnrollmentDateGroup.cs
avec le code suivant :
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; }
}
}
Mettre à jour le modèle de page About
Les modèles web dans ASP.NET Core 2.2 n’incluent pas la page About. Si vous utilisez ASP.NET Core 2.2, créez la page About Razor Page.
Mettez à jour le fichier Pages/About.cshtml.cs
avec le code suivant :
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();
}
}
}
L’instruction LINQ regroupe les entités Student par date d’inscription, calcule le nombre d’entités dans chaque groupe et stocke les résultats dans une collection d’objets de modèle de vue EnrollmentDateGroup
.
Modifier la page About Razor
Remplacez le code du fichier Pages/About.cshtml
par le code suivant :
@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>
Exécutez l’application et accédez à la page About. Le nombre d’étudiants pour chaque date d’inscription s’affiche dans une table.
Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez l’application terminée pour cette phase.
Ressources supplémentaires
Dans le didacticiel suivant, l’application utilise des migrations pour mettre à jour le modèle de données.
Commentaires
https://aka.ms/ContentUserFeedback.
Bientôt disponible : Tout au long de 2024, nous allons supprimer progressivement GitHub Issues comme mécanisme de commentaires pour le contenu et le remplacer par un nouveau système de commentaires. Pour plus d’informations, consultezEnvoyer et afficher des commentaires pour