ASP.NET Core: Sắp xếp (sorting), lọc (filtering) và phân trang (paging)
Trong bài viết này
- Điều kiện tiên quyết
- Thêm liên kết sắp xếp cột
- Thêm hộp tìm kiếm
- Thêm phân trang vào Students Index
- Thêm phân trang vào phương thức Index
- Thêm liên kết phân trang
- Tạo trang About
- Lấy mã
- Bước tiếp theo
Trong hướng dẫn trước, bạn đã triển khai một tập hợp các trang web cho các thao tác CRUD cơ bản cho các thực thể Student. Trong hướng dẫn này, bạn sẽ thêm chức năng sắp xếp (sorting), lọc (filtering) và phân trang (paging) vào trang Students Index. Bạn cũng sẽ tạo một trang thực hiện việc nhóm (grouping) đơn giản.
Hình minh họa sau đây cho thấy trang sẽ trông như thế nào khi bạn hoàn tất. Tiêu đề cột là các liên kết mà người dùng có thể nhấp vào để sắp xếp theo cột đó. Nhấp vào tiêu đề cột liên tục sẽ chuyển đổi giữa thứ tự sắp xếp tăng dần và giảm dần.
Trong hướng dẫn này, bạn:
- Thêm liên kết (link) sắp xếp cột
- Thêm hộp tìm kiếm
- Thêm phân trang vào Students Index
- Thêm phân trang vào phương thức Index
- Thêm liên kết (link) phân trang
- Tạo trang Aboute
Điều kiện tiên quyết
Thêm liên kết sắp xếp cột
Để thêm tính năng sắp xếp vào trang Students Index, bạn sẽ thay đổi phương thức Index
của controller Students và thêm mã vào view Students Index.
Thêm chức năng sắp xếp vào phương thức Index
Trong StudentsController.cs
, thay thế phương thức Index
bằng đoạn mã sau:
public async Task<IActionResult> Index(string sortOrder)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Mã này nhận được một tham số sortOrder
từ chuỗi truy vấn trong URL. Giá trị chuỗi truy vấn được ASP.NET Core MVC cung cấp dưới dạng tham số cho phương thức hành động. Tham số sẽ là một chuỗi có "Name" hoặc "Date", theo sau tùy chọn là dấu gạch dưới và chuỗi "desc" để chỉ định thứ tự giảm dần. Thứ tự sắp xếp mặc định là tăng dần.
Lần đầu tiên trang Index được yêu cầu, 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 của họ, đây là giá trị mặc định được thiết lập cho trường hợp dự phòng trong câu lệnh switch
. Khi người dùng bấm vào siêu liên kết tiêu đề cột, thì giá trị sortOrder
thích hợp sẽ được cung cấp trong chuỗi truy vấn.
Hai phần tử ViewData
(NameSortParm và DateSortParm) được view 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.
public async Task<IActionResult> Index(string sortOrder)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Đây là những câu lệnh ternary (sử dụng toán tử ?:). Lệnh đầu tiên chỉ định rằng nếu tham số sortOrder
rỗng hoặc trống thì NameSortParm phải được đặt thành "name_desc"; nếu không, nó phải được đặt thành một chuỗi trống. Hai câu lệnh này cho phép view đặ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 Last Name | Siêu liên kết Date |
---|---|---|
Last Name tăng dần | giảm dần | tăng dần |
Last Name giảm dần | tăng dần | tăng dần |
Last Name tăng dần | tăng dần | giảm dần |
Last Name giảm dần | tăng dần | tăng dần |
Phương thức này sử dụng LINQ to Entities để chỉ định cột cần sắp xếp. Mã tạo một biến IQueryable
trước câu lệnh switch, sửa đổi nó trong câu lệnh switch và gọi phương thức ToListAsync
sau câu lệnh switch
. Khi bạn tạo và sửa đổi các biến IQueryable
, không có truy vấn nào được gửi tới cơ sở dữ liệu. Truy vấn không được thực thi cho đến khi bạn chuyển đổi đối tượng IQueryable
thành một bộ sưu collection bằng cách gọi một phương thức như ToListAsync
. Do đó, mã này dẫn đến một truy vấn duy nhất không được thực thi cho đến khi có câu lệnh return View
.
Mã này có thể dài dòng với số lượng lớn các cột. Hướng dẫn cuối cùng trong loạt bài này cho thấy cách viết mã cho phép bạn truyền tên cột OrderBy
trong một biến chuỗi.
Thêm siêu liên kết tiêu đề cột vào chế view Students Index
Thay thế mã trong Views/Students/Index.cshtml
bằng mã sau để thêm siêu liên kết tiêu đề cột. Các dòng thay đổi được đánh dấu.
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model => model.LastName)</a>
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model => model.EnrollmentDate)</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<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-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Mã này sử dụng thông tin trong property ViewData
để thiết lập siêu liên kết với các giá trị chuỗi truy vấn thích hợp.
Chạy ứng dụng, chọn tab Students và nhấp vào tiêu đề cột Last Name và Enrollment Date để xác minh rằng việc sắp xếp có hiệu quả.
Thêm hộp tìm kiếm
Để thêm tính năng lọc (filtering) vào trang Students Index, bạn sẽ thêm hộp văn bản và nút gửi vào view và thực hiện các thay đổi tương ứng trong phương thức Index
. Hộp văn bản sẽ cho phép bạn nhập một chuỗi để tìm kiếm trong trường first name và last name.
Thêm chức năng lọc vào phương thức Index
Trong StudentsController.cs
, thay thế phương thức Index
bằng đoạn mã sau (các thay đổi được đánh dấu).
public async Task<IActionResult> Index(string sortOrder, string searchString)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Bạn đã thêm một tham số searchString
vào phương thức Index
. Giá trị chuỗi tìm kiếm được nhận từ hộp văn bản mà bạn sẽ thêm vào view Index. Bạn cũng đã thêm vào câu lệnh LINQ một mệnh đề Where chỉ 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 thêm mệnh đề Where chỉ được thực thi nếu có giá trị cần tìm kiếm.
Ghi chú
Ở đây bạn đang gọi phương thức
Where
trên một đối tượngIQueryable
và bộ lọc sẽ được xử lý trên máy chủ. Trong một số trường hợp, bạn có thể gọi phương thứcWhere
này là phương thức mở rộng trên collection trong bộ nhớ. (Ví dụ: giả sử bạn thay đổi tham chiếu thành_context.Students
để thay vì EFDbSet
thì nó tham chiếu một phương thức kho lưu trữ trả về một collectionIEnumerable
.) Kết quả thường giống nhau nhưng trong một số trường hợp có thể khác.Ví dụ: việc triển khai phương thức .NET Framework
Contains
thực hiện so sánh phân biệt chữ hoa chữ thường theo mặc định, nhưng trong SQL Server, điều này được xác định bởi cài đặt đối chiếu của phiên bản SQL Server. Cài đặt đó mặc định không phân biệt chữ hoa chữ thường. Bạn có thể gọi phương thứcToUpper
để thực hiện kiểm tra không phân biệt chữ hoa chữ thường: Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper()). Điều đó sẽ đảm bảo rằng kết quả vẫn giữ nguyên nếu bạn thay đổi mã sau này để sử dụng một kho lưu trữ trả về một collectionIEnumerable
thay vì một đối tượngIQueryable
. (Khi bạn gọi phương thứcContains
trên một collectionIEnumerable
, bạn sẽ có được triển khai .NET Framework; khi bạn gọi nó trên một đối tượngIQueryable
, bạn sẽ có được cách triển khai của nhà cung cấp cơ sở dữ liệu.) Tuy nhiên, có một hình phạt về hiệu suất cho giải pháp này. MãToUpper
sẽ đặt một hàm trong mệnh đề WHERE của câu lệnh TSQL SELECT. Điều đó sẽ ngăn trình tối ưu hóa sử dụng một chỉ mục. Vì SQL hầu hết được cài đặt ở dạng không phân biệt chữ hoa chữ thường, tốt nhất bạn nên tránh mãToUpper
cho đến khi bạn di chuyển sang kho lưu trữ dữ liệu phân biệt chữ hoa chữ thường.
Thêm hộp tìm kiếm vào view Students Index
Trong Views/Student/Index.cshtml
, hãy thêm mã được đánh dấu ngay trước thẻ <table> để tạo tiêu đề, hộp văn bản và nút Search.
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
<table class="table">
Mã này sử dụng trình trợ giúp thẻ <form>
để thêm hộp văn bản và nút tìm kiếm. Theo mặc định, trình trợ giúp thẻ <form>
gửi dữ liệu form bằng POST, có nghĩa là các tham số được truyền trong nội dung thông báo HTTP chứ không phải trong URL dưới dạng chuỗi truy vấn. Khi bạn chỉ định HTTP GET, dữ liệu form sẽ được chuyển vào URL dưới dạ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 bạn nên sử dụng GET khi hành động đó không dẫn đến bản cập nhật.
Chạy ứng dụng, chọn tab Students, nhập chuỗi tìm kiếm và nhấp vào Search để xác minh rằng tính năng lọc đang hoạt động.
Lưu ý rằng URL chứa chuỗi tìm kiếm.
http://localhost:5813/Students?SearchString=an
Nếu bạn đánh dấu trang này, bạn sẽ nhận được danh sách được lọc khi sử dụng dấu trang. Việc thêm method="get"
vào thẻ form
là nguyên nhân tạo ra chuỗi truy vấn.
Ở giai đoạn này, nếu bạn bấm vào liên kết sắp xếp tiêu đề cột, bạn sẽ mất giá trị bộ lọc mà bạn đã nhập vào hộp Search. Bạn sẽ khắc phục điều đó trong phần tiếp theo.
Thêm phân trang vào Students Index
Để thêm phân trang vào trang Students Index, bạn sẽ tạo một lớp PaginatedList
sử dụng các câu lệnh Skip
và Take
để lọc dữ liệu trên máy chủ thay vì luôn truy xuất tất cả các hàng của bảng. Sau đó, bạn sẽ thực hiện các thay đổi bổ sung trong phương thức Index
và thêm các nút phân trang vào view Index
. Hình minh họa sau đây cho thấy các nút phân trang.
Trong thư mục dự án, tạo PaginatedList.cs
và sau đó thay thế mã mẫu bằng mã 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 mã này lấy kích thước trang và số trang rồ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ó thể được sử dụng để bật hoặc tắt các nút phân trang Previous và Next.
Một phương thức CreateAsync
được sử dụng thay cho hàm tạo để tạo đối tượng PaginatedList<T>
vì hàm tạo không thể chạy mã không đồng bộ.
Thêm phân trang vào phương thức Index
Trong StudentsController.cs
, thay thế phương thức Index
bằng đoạn mã sau.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
}
Mã này thêm tham số số trang, tham số thứ tự sắp xếp hiện tại và tham số bộ lọc hiện tại vào signature của phương thức.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
Lần đầu tiên trang được hiển thị hoặc nếu người dùng chưa nhấp vào liên kết phân trang hoặc sắp xếp, tất cả các tham số sẽ không có giá trị. Nếu một liên kết phân trang được nhấp vào, biến trang sẽ chứa số trang cần hiển thị.
Phần tử ViewData
có tên CurrentSort cung cấp view theo thứ tự sắp xếp hiện tại, bởi vì điều này phải được đưa vào các liên kết phân trang để giữ nguyên thứ tự sắp xếp trong khi phân trang.
Phần tử ViewData
có tên CurrentFilter cung cấp view với chuỗi bộ lọc (filter) hiện tại. Giá trị này phải được bao gồm trong các liên kết phân trang để duy trì cài đặt bộ lọc trong khi phân trang và nó phải được khôi phục vào 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 quá trình phân trang, trang phải được đặt lại về 1, vì bộ lọc mới có thể hiển thị dữ liệu khác. Chuỗi tìm kiếm được thay đổi khi một giá trị được nhập vào hộp văn bản và nhấn nút Submit. Trong trường hợp đó, tham số searchString
không rỗng.
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
Khi kết thúc phương thức Index
, phương thức PaginatedList.CreateAsync
sẽ chuyển đổi truy vấn của sinh viên thành một trang gồm các sinh viên thuộc loại tập hợp hỗ trợ phân trang. Sau đó, trang duy nhất của sinh viên sẽ được chuyển đến view.
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
Phương thức PaginatedList.CreateAsync
lấy một số trang. Hai dấu hỏi đại diện cho toán tử hợp nhất null. Toán tử kết hợp null xác định giá trị mặc định cho kiểu có thể null; biểu thức (pageNumber ?? 1)
có nghĩa là trả về giá trị pageNumber
nếu nó có giá trị hoặc trả về 1 nếu pageNumber
có giá trị rỗng.
Thêm liên kết phân trang
Trong Views/Students/Index.cshtml
, thay thế mã hiện có bằng mã sau. Những thay đổi được đánh dấu.
@model PaginatedList<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
</th>
<th>
First Name
</th>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<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-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@{
var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex + 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @nextDisabled">
Next
</a>
Câu lệnh @model
ở đầu trang chỉ định rằng view hiện nhận được đối tượng PaginatedList<T>
thay vì đối tượng List<T>
.
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 tới controller để người dùng có thể sắp xếp trong kết quả lọc:
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>
Các nút phân trang được hiển thị bởi trình trợ giúp thẻ:
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>
Chạy ứng dụng và đi tới trang Students.
Nhấp vào các liên kết phân trang theo thứ tự sắp xếp khác nhau để đảm bảo phân trang hoạt động. Sau đó, nhập chuỗi tìm kiếm và thử phân trang lại để xác minh rằng phân trang cũng hoạt động chính xác với tính năng sắp xếp và lọc.
Tạo trang About
Đối với trang About của website Đại học Contoso, bạn sẽ hiển thị số lượng sinh viên đã đăng ký cho mỗi ngày đăng ký. Điều này đòi hỏi phải nhóm và tính toán đơn giản trên các nhóm. Để thực hiện điều này, bạn sẽ làm như sau:
- Tạo lớp view model cho dữ liệu mà bạn cần truyền đến view.
- Tạo phương thức About trong controller Home.
- Tạo view About.
Tạo view model
Tạo thư mục SchoolViewModels trong thư mục Models.
Trong thư mục mới, thêm file lớp EnrollmentDateGroup.cs
và thay thế mã mẫu bằng mã 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; }
}
}
Sửa đổi controller Home
Trong HomeController.cs
, thêm các câu lệnh sử dụng sau vào đầu tệp:
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;
Thêm một biến lớp cho ngữ cảnh cơ sở dữ liệu ngay sau dấu ngoặc nhọn mở cho lớp và lấy một phiên bản của ngữ cảnh từ ASP.NET Core DI:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly SchoolContext _context;
public HomeController(ILogger<HomeController> logger, SchoolContext context)
{
_logger = logger;
_context = context;
}
Thêm phương thức About
với đoạn mã sau:
public async Task<ActionResult> About()
{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
return View(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 collection của các đối tượng view model EnrollmentDateGroup
.
Tạo view About
Thêm một file Views/Home/About.cshtml
với mã sau đây:
@model IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>
@{
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)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
Chạy ứng dụng và đi đến trang About. Số lượng sinh viên cho mỗi ngày nhập học được hiển thị trong bảng.
Lấy mã
Tải xuống hoặc xem ứng dụng đã hoàn thành.
Bước tiếp theo
Nhưng vậy trong hướng dẫn này, bạn:
- Đã thêm liên kết sắp xếp cột
- Đã thêm hộp tìm kiếm Search
- Đã thêm phân trang vào Students Index
- Đã thêm phân trang vào phương thức Index
- Đã thêm liên kết phân trang
- Đã tạo trang About
Chuyển sang hướng dẫn tiếp theo để tìm hiểu cách xử lý các thay đổi của mô hình dữ liệu bằng cách sử dụng tính năng migration.
Tiếp theo: Xử lý các thay đổi của mô hình dữ liệu