ASP.NET - MVC - Sắp xếp sách
Sắp xếp
Sắp xếp là một yêu cầu quan trọng không kém phân trang hay tìm kiếm. Thực tế nhóm chức năng tìm kiếm – sắp xếp – phân trang thường đi cùng với nhau khi hiển thị dữ liệu dưới dạng bảng.
Việc sắp xếp dữ liệu trong C# thực hiện rất đơn giản nhờ gọi phương thức LINQ OrderBy() hoặc OrderByDescending():
// sắp xếp tăng dần
var books = Books.OrderBy(b => b.Title).ToArray();
// sắp xếp giảm dần
var books = Books.OrderByDescending(b => b.Title).ToArray();
Sửa tập tin Controller/LinkController.cs
Để sắp xếp tăng hay giảm, trước hết bạn cần 1 biến tên là “sortOrder” (hay đặt bất cứ tên nào bạn muốn) để lưu trữ trạng thái sắp xếp tăng, giảm trên trường thuộc tính nào trong bảng Link. Sau đó, bạn cần 1 biến ViewBag để giữ trạng thái trên Views (giao diện) tên là NameSortParm.
public ActionResult Index(string sortOrder)
{
// 1. Thêm biến NameSortParm để biết trạng thái sắp xếp tăng, giảm ở View
ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
//2.Tạo câu truy vấn kết 3 bảng Book, Author, Category
var books = db.Books.Include(b => b.Author).Include(b => b.Category);
//3. Sắp xếp theo sortOrder
switch (sortOrder)
{
// 3.1 Nếu biến sortOrder sắp giảm thì sắp giảm theo Title
case "name_desc":
books = books.OrderByDescending(b => b.Title);
break;
// 3.2 Mặc định thì sẽ sắp tăng
default:
books = books.OrderBy(b => b.Title);
break;
}
// 4. Trả kết quả về Views
return View(books.ToList());
}
Cách hoạt động đoạn mã trên như sau.
- Đầu tiên, bạn truyền biến sortOrder vào phương thức Index, dòng code ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? “name_desc” : “”; dùng để kiểm tra giá trị của biến sortOrder, nếu biến này có giá trị rỗng thì đặt giá trị biến NameSortParm là “name_desc”, ngược lại là rỗng. Cách viết 1 dòng là nguyên tắc tam phân (toán tử ?) dùng để tinh giản mã nguồn. Nếu viết dài dòng thì sẽ như sau:
// Bạn nào code "gà" sẽ viết như sau, còn PRO thì như trên (1 dòng duy nhất)
if(String.IsNullOrEmpty(sortOrder))
{
ViewBag.NameSortParm = "name_desc";
}
else
{
ViewBag.NameSortParm = "";
}
- Tạo 1 câu truy vấn var books = db.Books.Include(b => b.Author).Include(b => b.Category); để lấy hết tất cả liên kết trong bảng.
- Bước này chúng ta dùng một mệnh đề switch … case (tối giản hơn so với if else) để lọc mệnh đề truy vấn theo sắp tăng hay giảm dựa theo trạng thái biến đầu vào sortOrder.
- Sau đó, chúng ta trả về kết quả truy vấn trên giao diện để hiển thị.
Sửa nội dung Views/Link/Index.cshtml
Ở tập tin View Index.cshtml bạn sửa như sau, chú ý đoạn cần thêm.
@model IEnumerable<BookStoreManager.Models.Book>
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Danh sách sách</h2>
<p>
@Html.ActionLink("Thêm sách mới", "Create", null, new { @class = "btn btn-warning" })
</p>
<table class="table">
<tr>
<th style="width:200px">
<!-- Đoạn cần thêm -->
@Html.ActionLink("Tên sách", "Index", new { sortOrder = ViewBag.NameSortParm })
<!-- Kết thúc -->
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Images)
</th>
<th>
@Html.DisplayNameFor(model => model.Published)
</th>
<th>
@Html.DisplayNameFor(model => model.Author.AuthorName)
</th>
<th>
@Html.DisplayNameFor(model => model.Category.CategoryName)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<img src="~/bookimages/@item.Images" />
</td>
<td>
@Html.DisplayFor(modelItem => item.Published)
</td>
<td>
@Html.DisplayFor(modelItem => item.Author.AuthorName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Category.CategoryName)
</td>
<td>
@Html.ActionLink("Sửa", "Edit", new { id = item.BookID }) |
@Html.ActionLink("Xem", "Details", new { id = item.BookID }) |
@Html.ActionLink("Xóa", "Delete", new { id = item.BookID })
</td>
</tr>
}
</table>
Bạn có thể thấy đoạn code trên, chúng ta chỉ thêm dòng mã @Html.ActionLink("Tên sách", "Index", new { sortOrder = ViewBag.NameSortParm }) dùng để tạo một liên kết (a href) để người dùng có thể nhấn vào để sắp xếp theo trường LinkName tăng hay giảm.
Đoạn @Html.ActionLink("Tên sách", "Index", new { sortOrder = ViewBag.NameSortParm }) có thể diễn dịch thành mã HTML như sau:
<a href="/?sortOrder=name_desc">Tên sách</a>
Sau đó, bạn thử chạy trình duyệt http://localhost:xxxx/Book để có thể xem kết quả như sau. Bạn có thể nhấp lên liên kết LinkName (màu xanh) để xem kết quả sắp xếp.
Khi bạn sắp giảm theo tên (Z->A) thì sẽ có đường dẫn http://localhost:xxxx/Book?sortOrder=name_desc, ngược lại sắp mặc định (tức A->Z) thì sẽ có đường dẫn http://localhost:45033/Book.
Sắp xếp theo nhiều thuộc tính
Ở phần trước, bạn đã tìm cách sắp xếp tăng/giảm theo thuộc tính Title. Trên thực tế, các ứng dụng Web luôn đòi hỏi sắp xếp nhiều thuộc tính hơn, do đó phần này sẽ chỉ bạn cách sắp xếp theo nhiều thuộc tính.
Để sắp xếp thứ tự theo thuộc tính Price bên cạnh Title đã có, bạn làm theo hướng dẫn sau.
Sửa tập tin Controller/BookController.cs
Bạn mở tập tin BookController.cs, tìm phương thức Index và sửa như sau.
public ActionResult Index(string sortOrder)
{
// 1. Thêm biến NameSortParm để biết trạng thái sắp xếp tăng, giảm ở View
ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewBag.Price = sortOrder == "price" ? "price_desc" : "price";
//2.Tạo câu truy vấn kết 3 bảng Book, Author, Category
var books = db.Books.Include(b => b.Author).Include(b => b.Category);
//3. Sắp xếp theo sortOrder
switch (sortOrder)
{
// 3.1 Nếu biến sortOrder sắp giảm thì sắp giảm theo Title
case "name_desc":
books = books.OrderByDescending(b => b.Title);
break;
// 3.2 Sắp tăng dần theo price
case "price":
books = books.OrderBy(b => b.Price);
break;
// 3.4 Sắp giảm theo price
case "price_desc":
books = books.OrderByDescending(b => b.Price);
break;
// 3.5 Mặc định thì sẽ sắp tăng
default:
books = books.OrderBy(b => b.Title);
break;
}
// 4. Trả kết quả về Views
return View(books.ToList());
}
Trong đoạn mã trên, bạn chú ý chúng ta thêm 1 biến ViewBag Price dùng để giữ trạng thái giá trị trên Views để biết khi nào sắp tăng hay giảm theo thuộc tính Price . Ở mệnh đề switch … case chúng ta cần thêm 2 trường hợp, nếu giá trị của sortOrder là “price” thì chúng ta sẽ sắp tăng theo Price, còn giá trị của sortOrder là “price_desc” thì sẽ sắp giảm.
Sửa nội dung Views/Link/Index.cshtml
Trên tập tin View, bạn chỉ cần sửa dòng @Html.ActionLink("Đơn giá","Index", new { sortOrder = ViewBag.Price}) để tạo ra một liên kết (a href) mà người sử dụng có thể nhấp chuột để sắp xếp trên giao diện.
@model IEnumerable<BookStoreManager.Models.Book>
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Danh sách sách</h2>
<p>
@Html.ActionLink("Thêm sách mới", "Create", null, new { @class = "btn btn-warning" })
</p>
<table class="table">
<tr>
<th style="width:200px">
<!-- Đoạn cần thêm -->
@Html.ActionLink("Tên sách", "Index", new { sortOrder = ViewBag.NameSortParm })
<!-- Kết thúc -->
</th>
<th>
@Html.ActionLink("Đơn giá","Index", new { sortOrder = ViewBag.Price})
</th>
<th>
@Html.DisplayNameFor(model => model.Images)
</th>
<th>
@Html.DisplayNameFor(model => model.Published)
</th>
<th>
@Html.DisplayNameFor(model => model.Author.AuthorName)
</th>
<th>
@Html.DisplayNameFor(model => model.Category.CategoryName)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<img src="~/bookimages/@item.Images" />
</td>
<td>
@Html.DisplayFor(modelItem => item.Published)
</td>
<td>
@Html.DisplayFor(modelItem => item.Author.AuthorName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Category.CategoryName)
</td>
<td>
@Html.ActionLink("Sửa", "Edit", new { id = item.BookID }) |
@Html.ActionLink("Xem", "Details", new { id = item.BookID }) |
@Html.ActionLink("Xóa", "Delete", new { id = item.BookID })
</td>
</tr>
}
</table>
Sau đó, bạn build dự án và chạy đường dẫn http://localhost:xxxx/Book để xem kết quả với giao diện như sau.
Sau đây là bảng ghi lại giá trị của các liên kết LinkName và LinkDescription theo thứ tự sắp xếp.
Thứ tự sắp xếp hiện tại | Liên kết Title | Liên kết Price |
---|---|---|
LinkName ascending | descending | ascending |
LinkName descending | ascending | descending |
LinkDescription ascending | ascending | descending |
LinkDescription descending | ascending | ascending |
Sắp xếp theo nhiều thuộc tính (nâng cao)
Trong ví dụ trên chúng ta chỉ sắp xếp đơn giản chỉ vài trường dữ liệu. Tuy nhiên, trong ứng dụng web, bạn thường phải sắp xếp dữ liệu theo yêu cầu của người dùng. Yêu cầu này thường đến dưới dạng query string của URL. Ở mứ độ đơn giản nhất, người dùng thường muốn chỉ định sắp xếp dữ liệu theo trường nào và theo thứ tự nào (tăng dần/giảm dần).
Với dữ liệu sách, người dùng có thể muốn sắp xếp theo một trong bốn tiêu chí: tiêu đề, tác giả, năm xuất bản, nhà xuất bản.
Bạn có thể sử dụng cấu trúc if-else hoặc switch để gọi cặp OrderBy/OrderByDescending theo đúng tiêu chí người dùng mong muốn.
Tuy nhiên, hãy hình dung nếu dữ liệu của bạn có rất nhiều property làm tiêu chí sắp xếp. Khi này, code của bạn sẽ phình to ra.
Có một giải pháp đơn giản hơn cho vấn đề này. Bạn có thể sử dụng một thư viện class hỗ trợ sử dụng hàm LINQ nhưng với tham số là chuỗi ký tự, thay vì hàm lambda như nguyên bản.
Hãy hình dung bạn có thể gọi
var books = Books.OrderBy("Title").ToList();
var books = Books.OrderBy("Title desc").ToList();
Mặc dù nhìn không quá khác biệt nhưng cách gọi thứ hai có ưu điểm rất lớn trong ứng dụng web: Bạn có thể nhận trực tiếp tiêu chí sắp xếp từ truy vấn (qua string query) và truyền vào phương thức. Không cần if-else hay switch-case nữa. Nó giúp code của bạn đơn giản đi rất nhiều.
Cài đặt Dynamic LINQ 1.0.7
Chúng ta phải sử dụng thư viện này để tạo 1 số câu truy vấn sql theo kiểu cũ thông qua 1 số hàm hỗ trợ. Trong Visual Studio, bạn vào Tools -> Library Package Manager -> Manage NuGet Packages…
Sau đó gõ Dynamic Linq bên ô Search góc trái trên cùng, chọn gói System.Linq.Dynamic để cài đặt.
Sửa tập tin Controller/LinkController.cs
Bạn sửa phương thức Index của tập tin Controller/BookController.cs này như sau.
using System.Linq.Dynamic; // nhúng vào tập tin
using System.Linq.Expressions; // nhúng vào tập tin
.......
public ActionResult Index(string sortProperty, string sortOrder)
{
// 1. Tạo biến ViewBag SortOrder để giữ trạng thái sắp tăng hay giảm
ViewBag.SortOrder = String.IsNullOrEmpty(sortOrder) ? "desc" : "";
//2.Tạo câu truy vấn kết 2 bảng Book, Author, Category
var books = db.Books.Include(b => b.Author).Include(b => b.Category);
// 4. Tạo thuộc tính sắp xếp mặc định là "Title"
if (String.IsNullOrEmpty(sortProperty)) sortProperty = "Title";
// 5. Sắp xếp tăng/giảm bằng phương thức OrderBy sử dụng trong thư viện Dynamic LINQ
if (sortOrder == "desc")
books = books.OrderBy(sortProperty + " desc");
else
books = books.OrderBy(sortProperty);
// 4. Trả kết quả về Views
return View(books.ToList());
}
Trong đoạn mã trên, bạn có thể thấy chú thích giải thích từng đoạn. Trước hết lưu ý nhúng 2 dòng using System.Linq.Dynamic; và using System.Linq.Expressions; để sử dụng thư viện Dynamic LINQ khi truy vấn.
Sau đó, chúng ta sử dụng biến ViewBag SortOrder để giữ trạng thái sắp xếp tăng/giảm (desc là giảm và mặc định là tăng).
Kế đến, bạn tạo câu truy vấn và đặt giá trị sortProperty (thuộc tính sắp xếp) mặc định là “Title”. Tiếp theo, thêm câu truy vấn sắp tăng hay giảm. Ở bước này, chú ý bạn phải cài thư viện Dynamic LINQ 1.0.7 ở trên mới có thể sử dụng phương thức OrderBy với giá trị chuỗi truyền vào. Cuối cùng trả kết quả truy vấn về View để hiển thị.
Sửa tập tin Views/Link/Index.cshtml
Bạn sửa tập tin này như sau, chú ý trong đoạn @Html.ActionLink("Tác giả", "Index", new { sortProperty = "AuthorID", sortOrder = ViewBag.SortOrder }). Trong bảng Book chúng ta chỉ sort tác giản theo AuthorID mà thôi
@model IEnumerable<BookStoreManager.Models.Book>
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Danh sách sách</h2>
<p>
@Html.ActionLink("Thêm sách mới", "Create", null, new { @class = "btn btn-warning" })
</p>
<table class="table">
<tr>
<th style="width:200px">
<!-- Đoạn cần thêm -->
@Html.ActionLink("Tên sách", "Index", new { sortProperty = "Title", sortOrder = ViewBag.SortOrder })
<!-- Kết thúc -->
</th>
<th>
@Html.ActionLink("Đơn giá", "Index", new { sortProperty = "Price", sortOrder = ViewBag.SortOrder })
</th>
<th>
@Html.ActionLink("Ngày xuất bản", "Index", new { sortProperty = "Published", sortOrder = ViewBag.SortOrder })
</th>
<th>
@Html.ActionLink("Tác giả", "Index", new { sortProperty = "AuthorID", sortOrder = ViewBag.SortOrder })
</th>
<th>
@Html.DisplayNameFor(model => model.Category.CategoryName)
</th>
<th>
@Html.DisplayNameFor(model => model.Images)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Published)
</td>
<td>
@Html.DisplayFor(modelItem => item.Author.AuthorName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Category.CategoryName)
</td>
<td>
<img src="~/bookimages/@item.Images" />
</td>
<td>
@Html.ActionLink("Sửa", "Edit", new { id = item.BookID }) |
@Html.ActionLink("Xem", "Details", new { id = item.BookID }) |
@Html.ActionLink("Xóa", "Delete", new { id = item.BookID })
</td>
</tr>
}
</table>
Như vậy là xong, bạn mở trình duyệt chạy URL http://localhost:xxxx/Book/Index và thử sắp xếp các cột, bạn sẽ được kết quả như ý. Tuyệt vời phải không nào?
Kết luận
Đến đây bạn đã học cách sắp xếp trong ASP.NET MVC theo nhiều cách khác nhau, tùy theo yêu cầu dự án Web bạn có thể phát triển việc sắp xếp theo nhiều cách mới lạ và độc đáo nữa.