ASP.NET Core: Razor Pages với Entity Framework Core trong ASP.NET Core - Sort, Filter, Paging - Hướng dẫn 3/8
Ứng dụng web của Đại học Contoso trình bày cách tạo các ứng dụng web Razor Pages bằng EF Core và Visual Studio. Để biết thông tin về loạt bài hướng dẫn, hãy xem phần hướng dẫn đầu tiên.
Nếu bạn gặp sự cố mà bạn không thể giải quyết, hãy tải xuống ứng dụng đã hoàn thiện và so sánh với code bạn đã tạo bằng cách làm theo hướng dẫn.
Hướng dẫn này thêm chức năng sắp xếp (sort), lọc (filter) và phân trang (paging) cho các trang Student.
Hình minh họa sau đây cho thấy một trang đã hoàn thành. Các tiêu đề cột là các liên kết có thể nhấp để sắp xếp cột. Nhấp vào tiêu đề cột nhiều lần để chuyển đổi giữa thứ tự sắp xếp tăng dần và giảm dần.
Thêm sắp xếp (sort)
Thay thế code trong Pages/Students/Index.cshtml.cs bằng đoạn code sau đây để thêm phần sắp xếp.
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();
}
}
Trong đoạn code trên:
- Yêu cầu thêm
using System;. - Thêm các property để chứa các tham số sắp xếp.
- Thay đổi tên của property
StudentthànhStudents. - Thay thế code trong phương thức
OnGetAsync.
Phương thức OnGetAsync nhận một tham số sortOrder từ chuỗi truy vấn trong URL. URL và chuỗi truy vấn được tạo bởi Trình trợ giúp thẻ neo.
Tham số sortOrder là hoặc Name hoặc Date. Tham số sortOrder được tùy chọn theo sau _desc để chỉ định thứ tự giảm dần. Thứ tự sắp xếp mặc định là tăng dần.
Khi trang Index được yêu cầu từ liên kết Students, sẽ không có chuỗi truy vấn nào. Các sinh viên được hiển thị theo thứ tự tăng dần theo họ (last name). Thứ tự tăng dần theo họ là thứ tự default trong câu lệnh switch. Khi người dùng bấm vào liên kết tiêu đề cột, giá trị sortOrder phù hợp sẽ được cung cấp trong giá trị chuỗi truy vấn.
NameSort và DateSort được Razor Page sử dụng để định cấu hình các siêu liên kết tiêu đề cột với các giá trị chuỗi truy vấn thích hợp:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
Đoạn code trên sử dụng toán tử điều kiện C# ?:. Toán tử ?: là một toán tử bậc ba (ba ngôi), nó có ba toán hạng. Dòng đầu tiên chỉ định rằng khi sortOrder rỗng hoặc trống, thì NameSort được đặt thành name_desc. Nếu sortOrder không phải là null hoặc trống, thì NameSort được đặt thành một chuỗi trống.
Hai câu lệnh này cho phép trang đặt các siêu liên kết tiêu đề cột như sau:
| Thứ tự sắp xếp hiện tại | Siêu liên kết họ (last name) | Siêu liên kết ngày |
|---|---|---|
| Họ tăng dần | giảm dần | tăng dần |
| Họ giảm dần | tăng dần | tăng dần |
| Ngày tăng dần | tăng dần | giảm dần |
| Ngày giảm dần | tăng dần | tăng dần |
Phương pháp này sử dụng LINQ to Entities để chỉ định cột để sắp xếp theo. Đoạn code sau khởi tạo một IQueryable<Student> trước câu lệnh switch và sửa đổi nó trong câu lệnh 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();
Khi một IQueryable được tạo hoặc sửa đổi, không có truy vấn nào được gửi đến cơ sở dữ liệu. Truy vấn không được thực thi cho đến khi đối tượng IQueryable được chuyển đổi thành collection. IQueryable được chuyển đổi thành một collection bằng cách gọi một phương thức như ToListAsync. Do đó, code IQueryable cho kết quả trong một truy vấn duy nhất không được thực hiện cho đến câu lệnh sau:
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync có thể trở nên dài dòng với một số lượng lớn các cột có thể sắp xếp. Để biết thông tin về một cách khác để viết mã chức năng này, hãy xem Sử dụng LINQ động để đơn giản hóa code trong phiên bản MVC của loạt bài hướng dẫn này.
Thêm siêu liên kết tiêu đề cột vào trang Student Index
Thay thế code trong Students/Index.cshtml bằng code sau đây. Những thay đổi được đánh dấu.
@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>
Trong đoạn code trên:
- Thêm siêu liên kết vào tiêu đề cột
LastNamevàEnrollmentDate. - Sử dụng thông tin trong
NameSortvàDateSortđể thiết lập siêu liên kết với các giá trị thứ tự sắp xếp hiện tại. - Thay đổi tiêu đề trang từ Index thành Student.
- Thay đổi
Model.StudentthànhModel.Students.
Để xác minh rằng sắp xếp hoạt động:
- Chạy ứng dụng và chọn tab Student.
- Nhấp vào tiêu đề cột.
Thêm bộ lọc (filter)
Để thêm tính năng lọc vào trang Student Index:
- Một hộp văn bản và một nút gửi được thêm vào Razor Page. Hộp văn bản cung cấp một chuỗi tìm kiếm trên tên hoặc họ.
- Mô hình trang được cập nhật để sử dụng giá trị hộp văn bản.
Cập nhật phương thức OnGetAsync
Thay thế code trong Students/Index.cshtml.cs bằng code sau đây để thêm bộ lọc:
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();
}
}
Trong đoạn code trên:
- Thêm tham số
searchStringvào phương thứcOnGetAsyncvà lưu giá trị tham số trong propertyCurrentFilter. Giá trị chuỗi tìm kiếm được nhận từ hộp văn bản (text box) được thêm vào trong phần tiếp theo. - Thêm vào câu lệnh LINQ một mệnh đề
Where. Mệnh đềWherechỉ chọn những sinh viên có tên hoặc họ chứa chuỗi tìm kiếm. Câu lệnh LINQ chỉ được thực thi nếu có một giá trị để tìm kiếm.
So sánh IQueryable với IEnumerable
Đoạn code trên gọi phương thức Where trên một đối tượng IQueryable và bộ lọc được xử lý trên máy chủ. Trong một số trường hợp, ứng dụng có thể đang gọi phương thức Where dưới dạng phương thức mở rộng trên collection trong bộ nhớ. Ví dụ: giả sử _context.Students thay đổi từ EF Core DbSet sang một phương thức repository mà trả về một collection IEnumerable. Kết quả thường sẽ giống nhau nhưng trong một số trường hợp có thể khác.
Ví dụ: triển khai .NET Framework thực thi Contains so sánh phân biệt chữ hoa chữ thường theo mặc định. Trong SQL Server, Contains phân biệt chữ hoa chữ thường được xác định bởi cài đặt đối chiếu của phiên bản SQL Server. SQL Server mặc định không phân biệt chữ hoa chữ thường. SQLite mặc định phân biệt chữ hoa chữ thường. ToUpper có thể được gọi để làm cho bài kiểm tra không phân biệt chữ hoa chữ thường:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
Đoạn code trên sẽ đảm bảo rằng bộ lọc phân biệt chữ hoa chữ thường ngay cả khi phương thức Where được gọi trên một IEnumerable hoặc chạy trên SQLite.
Khi Contains được gọi trên một collection IEnumerable, thi triển khai .NET Core được sử dụng. Khi Contains được gọi trên một đối tượng IQueryable, việc triển khai cơ sở dữ liệu được sử dụng.
Gọi Contains trên một IQueryable thường được ưu tiên hơn vì lý do hiệu suất. Với IQueryable, việc filter được thực hiện bởi máy chủ cơ sở dữ liệu. Nếu IEnumerable được tạo trước, thì tất cả các hàng phải được trả về từ máy chủ cơ sở dữ liệu.
Có một hình phạt về hiệu suất khi gọi ToUpper. Đoạn code ToUpper thêm một chức năng trong mệnh đề WHERE của câu lệnh TSQL SELECT. Hàm được thêm vào ngăn trình tối ưu hóa sử dụng chỉ mục. Vì rằng SQL được cài đặt không phân biệt chữ hoa chữ thường, tốt nhất bạn nên tránh lời gọi ToUpper khi không cần thiết.
Để biết thêm thông tin, hãy xem Cách sử dụng truy vấn phân biệt chữ hoa chữ thường với nhà cung cấp Sqlite.
Cập nhật trang Razor
Thay code vào Pages/Students/Index.cshtml để thêm nút Search.
@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ạn code trên sử dụng tag helper <form> để thêm hộp văn bản tìm kiếm và nút. Theo mặc định, tag helper <form> gửi dữ liệu biểu mẫu bằng POST. Với POST, các tham số được truyền trong nội dung thư HTTP chứ không phải trong URL. Khi HTTP GET được sử dụng, form dữ liệu sẽ được chuyển vào URL dưới dạng chuỗi truy vấn. Truyền dữ liệu bằng chuỗi truy vấn cho phép người dùng đánh dấu URL. Nguyên tắc của W3C khuyến nghị nên sử dụng GET khi hành động không dẫn đến cập nhật.
Kiểm tra ứng dụng:
- Chọn tab Students và nhập chuỗi tìm kiếm. Nếu bạn đang sử dụng SQLite, bộ lọc chỉ phân biệt chữ hoa chữ thường nếu bạn đã triển khai code
ToUppertùy chọn được hiển thị trước đó. - Chọn Search.
Lưu ý rằng URL chứa chuỗi tìm kiếm. Ví dụ:
https://localhost:5001/Students?SearchString=an
Nếu trang được đánh dấu (bookmark), thì bookmark chứa URL của trang và chuỗi truy vấn SearchString. Thẻ method="get" trong thẻ form là nguyên nhân khiến chuỗi truy vấn được tạo.
Hiện tại, khi một liên kết sắp xếp tiêu đề cột được chọn, giá trị bộ lọc từ hộp Search sẽ bị mất. Giá trị bộ lọc bị mất được sửa trong phần tiếp theo.
Thêm phân trang (paging)
Trong phần này, một lớp PaginatedList được tạo ra để hỗ trợ phân trang. Lớp PaginatedList sử dụng câu lệnh Skip và Take để lọc dữ liệu trên máy chủ thay vì truy xuất tất cả các hàng của bảng. Hình minh họa sau đây cho thấy các nút phân trang.
Tạo lớp PaginatedList
Trong thư mục dự án, tạo PaginatedList.cs bằng đoạn code sau:
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);
}
}
}
Phương thức CreateAsync trong đoạn code trên lấy kích thước trang và số trang, đồng thời áp dụng các câu lệnh Skip và Take thích hợp cho file IQueryable. Khi ToListAsync được gọi trên IQueryable, nó sẽ trả về một List chỉ chứa trang được yêu cầu. Các property HasPreviousPage và HasNextPage được sử dụng để bật hoặc tắt các nút phân trang Previous và Next.
Phương thức CreateAsync được sử dụng để tạo file PaginatedList<T>. Một hàm tạo không thể tạo đối tượng PaginatedList<T>; các hàm tạo không thể chạy code không đồng bộ.
Thêm kích thước trang vào cấu hình
Thêm PageSize vào file Cấu hình appsettings.json:
{
"PageSize": 3,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Thêm phân trang vào IndexModel
Thay code sau vào Students/Index.cshtml.cs để thêm phân trang.
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);
}
}
}
Trong đoạn code trên:
- Thay đổi kiểu của property
StudentstừIList<Student>thànhPaginatedList<Student>. - Thêm chỉ mục trang, hiện tại
sortOrdervàcurrentFilterthành signature phương thứcOnGetAsync. - Lưu thứ tự sắp xếp trong property
CurrentSort. - Đặt lại chỉ mục trang thành 1 khi có chuỗi tìm kiếm mới.
- Sử dụng lớp
PaginatedListđể lấy các thực thể Student. - Đặt
pageSizethành 3 từ Cấu hình, 4 nếu cấu hình không thành công.
Tất cả các tham số OnGetAsync nhận được là null khi:
- Trang được gọi từ liên kết Students.
- Người dùng chưa nhấp vào liên kết phân trang hoặc sắp xếp.
Khi một liên kết phân trang được nhấp vào, biến chỉ mục trang chứa số trang sẽ hiển thị.
Property CurrentSort cung cấp Razor Page với thứ tự sắp xếp hiện tại. Thứ tự sắp xếp hiện tại phải được bao gồm trong các liên kết phân trang để giữ thứ tự sắp xếp trong khi phân trang.
Property CurrentFilter cung cấp Razor Page với chuỗi bộ lọc hiện tại. giá trị CurrentFilter:
- Phải được đưa vào trong các liên kết phân trang để duy trì cài đặt bộ lọc trong khi phân trang.
- Phải khôi phục hộp văn bản khi trang được hiển thị lại.
Nếu chuỗi tìm kiếm bị thay đổi trong khi phân trang, trang sẽ được đặt lại thành 1. Trang phải được đặt lại thành 1 vì bộ lọc mới có thể dẫn đến hiển thị dữ liệu khác. Khi một giá trị tìm kiếm được nhập và Submit được chọn thì:
- Chuỗi tìm kiếm được thay đổi.
- Tham số
searchStringkhông phải là null.
Phương thức PaginatedList.CreateAsync chuyển đổi truy vấn của sinh viên thành một trang duy nhất của sinh viên trong một loại collection hỗ trợ phân trang. Trang student duy nhất đó được chuyển đến Razor Page.
Hai dấu chấm hỏi sau pageIndex trong lời gọi PaginatedList.CreateAsync đại diện cho toán tử hợp nhất null. Toán tử hợp nhất null xác định giá trị mặc định cho loại có thể null. Biểu thức pageIndex ?? 1 trả về giá trị pageIndex nếu nó có giá trị, ngược lại, nó trả về 1.
Thêm liên kết phân trang
Thay thế code trong Students/Index.cshtml bằng code sau đây. Lưu ý những thay đổi được đánh dấu:
@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>
Các liên kết tiêu đề cột sử dụng chuỗi truy vấn để chuyển chuỗi tìm kiếm hiện tại cho phương thức OnGetAsync:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
Các nút phân trang được hiển thị bởi trình trợ giúp thẻ:
<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>
Chạy ứng dụng và điều hướng đến trang Students.
- Để đảm bảo phân trang hoạt động, hãy nhấp vào liên kết phân trang theo các thứ tự sắp xếp khác nhau.
- Để xác minh rằng phân trang hoạt động chính xác với sắp xếp và lọc, hãy nhập chuỗi tìm kiếm và thử phân trang.
Nhóm (Grouping)
Phần này tạo một trang About để hiển thị số lượng sinh viên đã đăng ký cho mỗi ngày đăng ký. Bản cập nhật sử dụng nhóm và bao gồm các bước sau:
- Tạo một view model xem cho dữ liệu được sử dụng bởi trang
About. - Cập nhật trang
Aboutđể sử dụng view model.
Tạo view model
Tạo thư mục Models/SchoolViewModels.
Tạo SchoolViewModels/EnrollmentDateGroup.cs với đoạn code sau:
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; }
}
}
Tạo Razor Page
Tạo một file Pages/About.cshtml có code như sau:
@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>
Tạo page model
Cập nhật file Pages/About.cshtml.cs với code như sau:
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();
}
}
}
Câu lệnh LINQ nhóm các thực thể sinh viên theo ngày đăng ký, tính toán số lượng thực thể trong mỗi nhóm và lưu trữ kết quả trong một tập hợp các đối tượng view model EnrollmentDateGroup.
Chạy ứng dụng và điều hướng đến trang Aboute. Số lượng sinh viên cho mỗi ngày ghi danh được hiển thị trong một bảng.
Bước tiếp theo
Trong hướng dẫn tiếp theo, ứng dụng sẽ sử dụng migration để cập nhật model dữ liệu.
Hướng dẫn trước Hướng dẫn tiếp theo