Parte 3, Páginas do Razor com EF Core no ASP.NET Core - Classificar, Filtrar, Paginar
Por Tom Dykstra, Jeremy Likness e Jon P. Smith
O aplicativo Web Contoso University demonstra como criar aplicativos Web das Razor Pages usando o EF Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.
Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e compare esse código com o que você criou seguindo o tutorial.
Este tutorial adiciona as funcionalidades de classificação, filtragem e paginação à página do Aluno.
A ilustração a seguir mostra uma página concluída. Os títulos de coluna são links clicáveis para classificar a coluna. Clique no título de coluna para alternar repetidamente entre a ordem de classificação ascendente e descendente.
Adicionar classificação
Substitua o código em Pages/Students/Index.cshtml.cs
pelo seguinte código para adicionar a classificação.
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();
}
}
O código anterior:
- Requer a adição de
using System;
. - Adiciona propriedades para conter os parâmetros de classificação.
- Altera o nome da propriedade
Student
paraStudents
. - Substitui o código no método
OnGetAsync
.
O método OnGetAsync
recebe um parâmetro sortOrder
da cadeia de caracteres de consulta na URL. A URL e a cadeia de caracteres de consulta são geradas pelo auxiliar de marcação da âncora.
O parâmetro sortOrder
é Name
ou Date
. O parâmetro sortOrder
é opcionalmente seguido de _desc
para especificar a ordem descendente. A ordem de classificação padrão é crescente.
Quando a página Índice é solicitada do link Alunos, não há nenhuma cadeia de caracteres de consulta. Os alunos são exibidos em ordem ascendente por sobrenome. A ordem ascendente por sobrenome é o default
na instrução switch
. Quando o usuário clica em um link de título de coluna, o valor sortOrder
apropriado é fornecido no valor de cadeia de caracteres de consulta.
NameSort
e DateSort
são usados pelo Páginas do Razor para configurar os hiperlinks de título da coluna com os valores de cadeia de caracteres de consulta apropriados:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
O código usa o operador condicional C# ?:. O operador ?:
é um operador ternário, utiliza três operandos. A primeira linha especifica que, quando sortOrder
é nulo ou vazio, NameSort
é definido como name_desc
. Se sortOrder
não é nulo nem vazio, NameSort
é definido como uma cadeia de caracteres vazia.
Essas duas instruções permitem que a página defina os hiperlinks de título de coluna da seguinte maneira:
Ordem de classificação atual | Hiperlink do sobrenome | Hiperlink de data |
---|---|---|
Sobrenome ascendente | descending | ascending |
Sobrenome descendente | ascending | ascending |
Data ascendente | ascending | descending |
Data descendente | ascending | ascending |
O método usa o LINQ to Entities para especificar a coluna pela qual classificar. O código inicializa um IQueryable<Student>
antes da instrução switch e modifica-o na instrução 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();
Quando um IQueryable
é criado ou modificado, nenhuma consulta é enviada ao banco de dados. A consulta não é executada até que o objeto IQueryable
seja convertido em uma coleção. IQueryable
são convertidos em uma coleção com uma chamada a um método como ToListAsync
. Portanto, o código IQueryable
resulta em uma única consulta que não é executada até que a seguinte instrução:
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync
pode ficar detalhado com um grande número de colunas classificáveis. Para obter informações sobre uma maneira alternativa de codificar essa funcionalidade, confira Usar o LINQ dinâmico para simplificar o código na versão MVC desta série de tutoriais.
Adicionar hiperlinks de título de coluna à página Student Index
Substitua o código em Students/Index.cshtml
pelo código a seguir. As alterações são realçadas.
@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>
O código anterior:
- Adiciona hiperlinks aos títulos de coluna
LastName
eEnrollmentDate
. - Usa as informações em
NameSort
eDateSort
para configurar hiperlinks com os valores de ordem de classificação atuais. - Altera o título da página de Índice para Alunos.
- Altera
Model.Student
paraModel.Students
.
Para verificar se a classificação funciona:
- Execute o aplicativo e selecione a guia Alunos.
- Clique nos títulos de coluna.
Adicionar filtragem
Para adicionar a filtragem à página Índice de Alunos:
- Uma caixa de texto e um botão Enviar são adicionados ao Páginas do Razor. A caixa de texto fornece uma cadeia de caracteres de pesquisa no nome ou sobrenome.
- O modelo de página é atualizado para usar o valor da caixa de texto.
Atualizar o método OnGetAsync
Substitua o código em Students/Index.cshtml.cs
pelo seguinte código para adicionar filtros:
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();
}
}
O código anterior:
- Adiciona o parâmetro
searchString
ao métodoOnGetAsync
e salva o valor do parâmetro na propriedadeCurrentFilter
. O valor de cadeia de caracteres de pesquisa é recebido de uma caixa de texto que é adicionada na próxima seção. - Adiciona uma cláusula
Where
à instrução LINQ. A cláusulaWhere
seleciona somente os alunos cujo nome ou sobrenome contém a cadeia de caracteres de pesquisa. A instrução LINQ é executada somente se há um valor a ser pesquisado.
IQueryable x IEnumerable
O código chama o método Where em um objeto IQueryable
, e o filtro é processado no servidor. Em alguns cenários, o aplicativo pode chamar o método Where
como um método de extensão em uma coleção em memória. Por exemplo, suponha que _context.Students
seja alterado do EF CoreDbSet
para um método de repositório que retorna uma coleção IEnumerable
. O resultado normalmente é o mesmo, mas em alguns casos pode ser diferente.
Por exemplo, a implementação do .NET Framework do Contains
executa uma comparação diferencia maiúsculas de minúsculas por padrão. No SQL Server, a diferenciação de maiúsculas e minúsculas de Contains
é determinada pela configuração de ordenação da instância do SQL Server. O SQL Server usa como padrão a não diferenciação de maiúsculas e minúsculas. O SQLite assume como padrão diferenciar maiúsculas de minúsculas. ToUpper
pode ser chamado para fazer com que o teste diferencie maiúsculas de minúsculas de forma explícita:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
O código anterior garantiria que o filtro não diferenciasse maiúsculas de minúsculas mesmo que o método Where
fosse chamado em um IEnumerable
ou fosse executado no SQLite.
Quando Contains
é chamado em uma coleção IEnumerable
, a implementação do .NET Core é usada. Quando Contains
é chamado em um objeto IQueryable
, a implementação do banco de dados é usada.
Chamar Contains
em um IQueryable
é geralmente preferível por motivos de desempenho. Com IQueryable
, a filtragem é feita pelo servidor de banco de dados. Se um IEnumerable
for criado primeiro, todas as linhas precisarão ser retornadas do servidor de banco de dados.
Há uma penalidade de desempenho por chamar ToUpper
. O código ToUpper
adiciona uma função à cláusula WHERE da instrução TSQL SELECT. A função adicionada impede que o otimizador use um índice. Considerando que o SQL é instalado como diferenciando maiúsculas de minúsculas, é melhor evitar a chamada ToUpper
quando ela não for necessária.
Para obter mais informações, confira Como usar consulta que não diferencia maiúsculas de minúsculas com o provedor SQLite.
Atualizar a página Razor
Substitua o código em Pages/Students/Index.cshtml
para adicionar um botão Pesquisar.
@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>
O código anterior usa a <form>
tag helper para adicionar a caixa de texto e o botão de pesquisa. Por padrão, o auxiliar de marcação <form>
envia dados de formulário com um POST. Com o POST, os parâmetros são passados no corpo da mensagem HTTP e não na URL. Quando o HTTP GET é usado, os dados de formulário são passados na URL como cadeias de consulta. Passar os dados com cadeias de consulta permite aos usuários marcar a URL. As diretrizes do W3C recomendam o uso de GET quando a ação não resulta em uma atualização.
Teste o aplicativo:
Selecione a guia Alunos e insira uma cadeia de caracteres de pesquisa. Se você estiver usando o SQLite, o filtro não diferenciará maiúsculas de minúsculas apenas se você tiver implementado o código opcional
ToUpper
mostrado anteriormente.Selecione Pesquisar.
Observe que a URL contém a cadeia de caracteres de pesquisa. Por exemplo:
https://localhost:5001/Students?SearchString=an
Se a página estiver marcada, o indicador conterá a URL para a página e a cadeia de caracteres de consulta SearchString
. O method="get"
na marcação form
é o que fez com que a cadeia de caracteres de consulta fosse gerada.
Atualmente, quando um link de classificação de título de coluna é selecionado, o valor de filtro da caixa Pesquisa é perdido. O valor de filtro perdido é corrigido na próxima seção.
Adicionar paginação
Nesta seção, uma classe PaginatedList
é criada para dar suporte à paginação. A classe PaginatedList
usa as instruções Skip
e Take
para filtrar dados no servidor em vez de recuperar todas as linhas da tabela. A ilustração a seguir mostra os botões de paginação.
Criar a classe PaginatedList
Na pasta do projeto, crie PaginatedList.cs
com o seguinte código:
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);
}
}
}
O método CreateAsync
no código anterior usa o tamanho da página e o número da página e aplica as instruções Skip
e Take
ao IQueryable
. Quando ToListAsync
é chamado no IQueryable
, ele retorna uma Lista que contém somente a página solicitada. As propriedades HasPreviousPage
e HasNextPage
são usadas para habilitar ou desabilitar os botões de paginação Anterior e Próximo.
O método CreateAsync
é usado para criar o PaginatedList<T>
. Um construtor não pode criar o objeto PaginatedList<T>
; construtores não podem executar um código assíncrono.
Adicionar o tamanho da página à configuração
Adicione PageSize
ao arquivo appsettings.json
Configuração:
{
"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"
}
}
Adicionar paginação ao IndexModel
Substitua o código em Students/Index.cshtml.cs
para adicionar a paginação.
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);
}
}
}
O código anterior:
- Altera o tipo da propriedade
Students
deIList<Student>
paraPaginatedList<Student>
. - Adiciona o índice de página, o
sortOrder
atual e ocurrentFilter
à assinatura do métodoOnGetAsync
. - Salva a ordem de classificação na propriedade
CurrentSort
. - Redefine o índice de página como 1 quando há uma nova cadeia de caracteres de pesquisa.
- Usa a classe
PaginatedList
para obter entidades de Aluno. - Define
pageSize
como 3 da Configuração, 4 se a configuração falhar.
Todos os parâmetros que OnGetAsync
recebe são nulos quando:
- A página é chamada no link Alunos.
- O usuário ainda não clicou em um link de paginação ou classificação.
Quando um link de paginação recebe um clique, a variável de índice de páginas contém o número da página a ser exibido.
A propriedade CurrentSort
fornece ao Páginas do Razor a ordem de classificação atual. A ordem de classificação atual precisa ser incluída nos links de paginação para que a ordem de classificação seja mantida durante a paginação.
A propriedade CurrentFilter
fornece ao Páginas do Razor a cadeia de caracteres do filtro atual. O valor CurrentFilter
:
- Deve ser incluído nos links de paginação para que as configurações de filtro sejam mantidas durante a paginação.
- Deve ser restaurado para a caixa de texto quando a página é exibida novamente.
Se a cadeia de caracteres de pesquisa é alterada durante a paginação, a página é redefinida como 1. A página precisa ser redefinida como 1, porque o novo filtro pode resultar na exibição de dados diferentes. Quando um valor de pesquisa é inserido e Enviar é selecionado:
- A cadeia de caracteres de pesquisa foi alterada.
- O parâmetro
searchString
não é nulo.
O método PaginatedList.CreateAsync
converte a consulta de alunos em uma única página de alunos de um tipo de coleção compatível com paginação. Essa única página de alunos é passada para o Páginas do Razor.
Os dois pontos de interrogação em pageIndex
na chamada PaginatedList.CreateAsync
representam o operador de união de nulo. O operador de união de nulo define um valor padrão para um tipo que permite valor nulo. A expressão pageIndex ?? 1
retorna o valor de pageIndex
se tiver um valor, caso contrário, retornará 1.
Adicionar links de paginação
Substitua o código em Students/Index.cshtml
pelo seguinte código. As alterações são realçadas:
@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>
Os links de cabeçalho de coluna usam a cadeia de caracteres de consulta para passar a cadeia de caracteres de pesquisa atual para o método OnGetAsync
:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
Os botões de paginação são exibidos por auxiliares de marcação:
<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>
Execute o aplicativo e navegue para a página de alunos.
- Para verificar se a paginação funciona, clique nos links de paginação em ordens de classificação diferentes.
- Para verificar se a paginação funciona corretamente com a classificação e filtragem, insira uma cadeia de caracteres de pesquisa e tente fazer a paginação.
Agrupamento
Essa seção cria uma página do About
que exibe quantos alunos se inscreveram para cada data de inscrição. A atualização usa o agrupamento e inclui as seguintes etapas:
- Crie um modelo de exibição para os dados usados pela página
About
. - Atualize a página
About
para usar o modelo de exibição.
Criar o modelo de exibição
Crie uma pasta Models/SchoolViewModels.
Crie SchoolViewModels/EnrollmentDateGroup.cs
com o seguinte código:
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; }
}
}
Criar a Página do Razor
Crie um arquivo Pages/About.cshtml
com o código a seguir:
@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>
Criar o modelo de página
Atualize o arquivo Pages/About.cshtml.cs
com o seguinte código:
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();
}
}
}
A instrução LINQ agrupa as entidades de alunos por data de registro, calcula o número de entidades em cada grupo e armazena os resultados em uma coleção de objetos de modelo de exibição EnrollmentDateGroup
.
Execute o aplicativo e navegue para a página Sobre. A contagem de alunos para cada data de registro é exibida em uma tabela.
Próximas etapas
No próximo tutorial, o aplicativo usa migrações para atualizar o modelo de dados.
Neste tutorial, as funcionalidades de classificação, filtragem, agrupamento e paginação são adicionadas.
A ilustração a seguir mostra uma página concluída. Os títulos de coluna são links clicáveis para classificar a coluna. Clicar em um título de coluna alterna repetidamente entre a ordem de classificação ascendente e descendente.
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído.
Adicionar uma classificação à página Índice
Adicione strings ao Students/Index.cshtml.cs
PageModel
para conter os parâmetros de classificação:
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; }
Atualize o Students/Index.cshtml.cs
OnGetAsync
com o seguinte código:
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();
}
O código anterior recebe um parâmetro sortOrder
da cadeia de caracteres de consulta na URL. A URL (incluindo a cadeia de caracteres de consulta) é gerada pelo Auxiliar de Marcação de Âncora
O parâmetro sortOrder
é "Name" ou "Date". Opcionalmente, o parâmetro sortOrder
é seguido por "_desc" para especificar a ordem descendente. A ordem de classificação padrão é crescente.
Quando a página Índice é solicitada do link Alunos, não há nenhuma cadeia de caracteres de consulta. Os alunos são exibidos em ordem ascendente por sobrenome. A ordem ascendente por sobrenome é o padrão (caso fall-through) na instrução switch
. Quando o usuário clica em um link de título de coluna, o valor sortOrder
apropriado é fornecido no valor de cadeia de caracteres de consulta.
NameSort
e DateSort
são usados pelo Páginas do Razor para configurar os hiperlinks de título da coluna com os valores de cadeia de caracteres de consulta apropriados:
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();
}
O seguinte código contém o operador ?: condicional do C#:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
A primeira linha especifica que, quando sortOrder
é nulo ou vazio, NameSort
é definido como "name_desc". Se sortOrder
não for nulo ou vazio, NameSort
será definido como uma cadeia de caracteres vazia.
O ?: operator
também é conhecido como o operador ternário.
Essas duas instruções permitem que a página defina os hiperlinks de título de coluna da seguinte maneira:
Ordem de classificação atual | Hiperlink do sobrenome | Hiperlink de data |
---|---|---|
Sobrenome ascendente | descending | ascending |
Sobrenome descendente | ascending | ascending |
Data ascendente | ascending | descending |
Data descendente | ascending | ascending |
O método usa o LINQ to Entities para especificar a coluna pela qual classificar. O código inicializa um IQueryable<Student>
antes da instrução switch e modifica-o na instrução 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();
}
Quando um IQueryable
é criado ou modificado, nenhuma consulta é enviada ao banco de dados. A consulta não é executada até que o objeto IQueryable
seja convertido em uma coleção. IQueryable
são convertidos em uma coleção com uma chamada a um método como ToListAsync
. Portanto, o código IQueryable
resulta em uma única consulta que não é executada até que a seguinte instrução:
Student = await studentIQ.AsNoTracking().ToListAsync();
OnGetAsync
pode ficar detalhado com um grande número de colunas classificáveis.
Adicionar hiperlinks de título de coluna à página Student Index
Substitua o código em Students/Index.cshtml
pelo seguinte código realçado:
@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>
O código anterior:
- Adiciona hiperlinks aos títulos de coluna
LastName
eEnrollmentDate
. - Usa as informações em
NameSort
eDateSort
para configurar hiperlinks com os valores de ordem de classificação atuais.
Para verificar se a classificação funciona:
- Execute o aplicativo e selecione a guia Alunos.
- Clique em Sobrenome.
- Clique em Data de Registro.
Para obter um melhor entendimento do código:
- Em
Students/Index.cshtml.cs
, defina um ponto de interrupção emswitch (sortOrder)
. - Adicione uma inspeção para
NameSort
eDateSort
. - Em
Students/Index.cshtml
, defina um ponto de interrupção em@Html.DisplayNameFor(model => model.Student[0].LastName)
.
Execute o depurador em etapas.
Adicionar uma Caixa de Pesquisa à página Índice de Alunos
Para adicionar a filtragem à página Índice de Alunos:
- Uma caixa de texto e um botão Enviar são adicionados ao Páginas do Razor. A caixa de texto fornece uma cadeia de caracteres de pesquisa no nome ou sobrenome.
- O modelo de página é atualizado para usar o valor da caixa de texto.
Adicionar a funcionalidade de filtragem a método Index
Atualize o Students/Index.cshtml.cs
OnGetAsync
com o seguinte código:
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();
}
O código anterior:
- Adiciona o parâmetro
searchString
ao métodoOnGetAsync
. O valor de cadeia de caracteres de pesquisa é recebido de uma caixa de texto que é adicionada na próxima seção. - Adicionou uma cláusula
Where
à instrução LINQ. A cláusulaWhere
seleciona somente os alunos cujo nome ou sobrenome contém a cadeia de caracteres de pesquisa. A instrução LINQ é executada somente se há um valor a ser pesquisado.
Observação: o código anterior chama o método Where
em um objeto IQueryable
, e o filtro é processado no servidor. Em alguns cenários, o aplicativo pode chamar o método Where
como um método de extensão em uma coleção em memória. Por exemplo, suponha que _context.Students
seja alterado do EF CoreDbSet
para um método de repositório que retorna uma coleção IEnumerable
. O resultado normalmente é o mesmo, mas em alguns casos pode ser diferente.
Por exemplo, a implementação do .NET Framework do Contains
executa uma comparação diferencia maiúsculas de minúsculas por padrão. No SQL Server, a diferenciação de maiúsculas e minúsculas de Contains
é determinada pela configuração de ordenação da instância do SQL Server. O SQL Server usa como padrão a não diferenciação de maiúsculas e minúsculas. ToUpper
pode ser chamado para fazer com que o teste diferencie maiúsculas de minúsculas de forma explícita:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
O código anterior garantirá que os resultados diferenciem maiúsculas de minúsculas se o código for alterado para usar IEnumerable
. Quando Contains
é chamado em uma coleção IEnumerable
, a implementação do .NET Core é usada. Quando Contains
é chamado em um objeto IQueryable
, a implementação do banco de dados é usada. O retorno de um IEnumerable
de um repositório pode ter uma penalidade significativa de desempenho:
- Todas as linhas são retornadas do servidor de BD.
- O filtro é aplicado a todas as linhas retornadas no aplicativo.
Há uma penalidade de desempenho por chamar ToUpper
. O código ToUpper
adiciona uma função à cláusula WHERE da instrução TSQL SELECT. A função adicionada impede que o otimizador use um índice. Considerando que o SQL é instalado como diferenciando maiúsculas de minúsculas, é melhor evitar a chamada ToUpper
quando ela não for necessária.
Adicionar uma Caixa de Pesquisa à página Student Index
Em Pages/Students/Index.cshtml
, adicione o código realçado a seguir para criar um botão Pesquisar e o cromado variado.
@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">
O código anterior usa a <form>
tag helper para adicionar a caixa de texto e o botão de pesquisa. Por padrão, o auxiliar de marcação <form>
envia dados de formulário com um POST. Com o POST, os parâmetros são passados no corpo da mensagem HTTP e não na URL. Quando o HTTP GET é usado, os dados de formulário são passados na URL como cadeias de consulta. Passar os dados com cadeias de consulta permite aos usuários marcar a URL. As diretrizes do W3C recomendam o uso de GET quando a ação não resulta em uma atualização.
Teste o aplicativo:
- Selecione a guia Alunos e insira uma cadeia de caracteres de pesquisa.
- Selecione Pesquisar.
Observe que a URL contém a cadeia de caracteres de pesquisa.
http://localhost:5000/Students?SearchString=an
Se a página estiver marcada, o indicador conterá a URL para a página e a cadeia de caracteres de consulta SearchString
. O method="get"
na marcação form
é o que fez com que a cadeia de caracteres de consulta fosse gerada.
Atualmente, quando um link de classificação de título de coluna é selecionado, o valor de filtro da caixa Pesquisa é perdido. O valor de filtro perdido é corrigido na próxima seção.
Adicionar a funcionalidade de paginação à página Índice de Alunos
Nesta seção, uma classe PaginatedList
é criada para dar suporte à paginação. A classe PaginatedList
usa as instruções Skip
e Take
para filtrar dados no servidor em vez de recuperar todas as linhas da tabela. A ilustração a seguir mostra os botões de paginação.
Na pasta do projeto, crie PaginatedList.cs
com o seguinte código:
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);
}
}
}
O método CreateAsync
no código anterior usa o tamanho da página e o número da página e aplica as instruções Skip
e Take
ao IQueryable
. Quando ToListAsync
é chamado no IQueryable
, ele retorna uma Lista que contém somente a página solicitada. As propriedades HasPreviousPage
e HasNextPage
são usadas para habilitar ou desabilitar os botões de paginação Anterior e Próximo.
O método CreateAsync
é usado para criar o PaginatedList<T>
. Um construtor não pode criar o objeto PaginatedList<T>
; construtores não podem executar um código assíncrono.
Adicionar a funcionalidade de paginação ao método Index
Em Students/Index.cshtml.cs
, atualize o tipo de Student
do IList<Student>
para PaginatedList<Student>
:
public PaginatedList<Student> Student { get; set; }
Atualize o Students/Index.cshtml.cs
OnGetAsync
com o seguinte código:
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);
}
O código anterior adiciona o índice de página, o sortOrder
atual e o currentFilter
à assinatura do método.
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
Todos os parâmetros são nulos quando:
- A página é chamada no link Alunos.
- O usuário ainda não clicou em um link de paginação ou classificação.
Quando um link de paginação recebe um clique, a variável de índice de páginas contém o número da página a ser exibido.
CurrentSort
fornece à Página do Razor a ordem de classificação atual. A ordem de classificação atual precisa ser incluída nos links de paginação para que a ordem de classificação seja mantida durante a paginação.
CurrentFilter
fornece à Página do Razor a cadeia de caracteres do filtro atual. O valor CurrentFilter
:
- Deve ser incluído nos links de paginação para que as configurações de filtro sejam mantidas durante a paginação.
- Deve ser restaurado para a caixa de texto quando a página é exibida novamente.
Se a cadeia de caracteres de pesquisa é alterada durante a paginação, a página é redefinida como 1. A página precisa ser redefinida como 1, porque o novo filtro pode resultar na exibição de dados diferentes. Quando um valor de pesquisa é inserido e Enviar é selecionado:
- A cadeia de caracteres de pesquisa foi alterada.
- O parâmetro
searchString
não é nulo.
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
O método PaginatedList.CreateAsync
converte a consulta de alunos em uma única página de alunos de um tipo de coleção compatível com paginação. Essa única página de alunos é passada para o Páginas do Razor.
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
Os dois pontos de interrogação em PaginatedList.CreateAsync
representam o operador de união de nulo. O operador de união de nulo define um valor padrão para um tipo que permite valor nulo. A expressão (pageIndex ?? 1)
significará retornar o valor de pageIndex
se ele tiver um valor. Se pageIndex
não tiver um valor, 1 será retornado.
Adicionar links de paginação à página do Razor do aluno
Atualize a marcação em Students/Index.cshtml
. As alterações são realçadas:
@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>
Os links de cabeçalho de coluna usam a cadeia de caracteres de consulta para passar a cadeia de caracteres de pesquisa atual para o método OnGetAsync
, de modo que o usuário possa classificar nos resultados do filtro:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
Os botões de paginação são exibidos por auxiliares de marcação:
<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>
Execute o aplicativo e navegue para a página de alunos.
- Para verificar se a paginação funciona, clique nos links de paginação em ordens de classificação diferentes.
- Para verificar se a paginação funciona corretamente com a classificação e filtragem, insira uma cadeia de caracteres de pesquisa e tente fazer a paginação.
Para obter um melhor entendimento do código:
- Em
Students/Index.cshtml.cs
, defina um ponto de interrupção emswitch (sortOrder)
. - Adicione uma inspeção para
NameSort
,DateSort
,CurrentSort
eModel.Student.PageIndex
. - Em
Students/Index.cshtml
, defina um ponto de interrupção em@Html.DisplayNameFor(model => model.Student[0].LastName)
.
Execute o depurador em etapas.
Atualizar a página Sobre para mostras estatísticas de alunos
Nesta etapa, Pages/About.cshtml
é atualizado para exibir quantos alunos se registraram para cada data de inscrição. A atualização usa o agrupamento e inclui as seguintes etapas:
- Criar um modelo de exibição para os dados usados pela página Sobre.
- Atualizar a página Sobre para usar o modelo de exibição.
Criar o modelo de exibição
Crie uma pasta SchoolViewModels na pasta Models.
Na pasta SchoolViewModels, adicione um EnrollmentDateGroup.cs
com o seguinte código:
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; }
}
}
Atualizar o modelo da página Sobre
Os modelos da Web no ASP.NET Core 2.2 não incluem a página Sobre. Se estiver usando o ASP.NET Core 2.2, crie a página Sobre o Razor.
Atualize o arquivo Pages/About.cshtml.cs
com o seguinte código:
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();
}
}
}
A instrução LINQ agrupa as entidades de alunos por data de registro, calcula o número de entidades em cada grupo e armazena os resultados em uma coleção de objetos de modelo de exibição EnrollmentDateGroup
.
Modificar a página Sobre o Razor
Substitua o código no arquivo Pages/About.cshtml
pelo seguinte código:
@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>
Execute o aplicativo e navegue para a página Sobre. A contagem de alunos para cada data de registro é exibida em uma tabela.
Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído para este estágio.
Recursos adicionais
No próximo tutorial, o aplicativo usa migrações para atualizar o modelo de dados.