Trong bài này, chúng ta sẽ thêm tính năng tìm kiếm đến ứng dụng MVCBooks theo Title hay Genre. Chúng ta sẽ sử dụng LINQ và nếu bạn chưa quen LINQ thì có thể truy cập https://ngocminhtran.com/linq-language-integrated-query/
Tìm kiếm theo Title
Mở tập tin BooksController.cshtml trong Controllers và tìm đến phương thức Index. Thay đổi nội dung Index như sau:
public async Task<IActionResult> Index(string searchString)
{
var books = from b in _context.Book
select b;
if (!String.IsNullOrEmpty(searchString))
{
books = books.Where(s => s.Title!.Contains(searchString));
}
return View(await books.ToListAsync());
}
Dòng mã đầu tiên chúng ta tạo một truy vấn LINQ (https://ngocminhtran.com/linq-language-integrated-query/ ) để chọn một đối tượng book. Nếu tham số searchString chứa thông tin tìm kiếm khác Null và khác chuỗi rỗng (kiểm tra nhờ phương thức IsNullOrObject của lớp String), các đối tượng chứa thông tin (trong trường hợp này là Title) trong searchString sẽ được hệ thống lọc và hiển thị:
var books = from b in _context.Book
select b;
if (!String.IsNullOrEmpty(searchString))
{
books = books.Where(s => s.Title!.Contains(searchString));
}
Có thể chạy thử ứng dụng trong trường hợp này bằng cách lọc thông tin các book có Title chứa chuỗi Habits với URL: https://localhost:44307/books?searchString=Habits

Nếu thay vì dùng tham số tên searchString, chúng ta thay đổi tên tham số là id khớp với id trong cấu hình từ Startup.cs:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Phương thức Index được thay đổi lại:
public async Task<IActionResult> Index(string id)
{
var books = from b in _context.Book
select b;
if (!String.IsNullOrEmpty(id))
{
books = books.Where(s => s.Title!.Contains(id));
}
return View(await books.ToListAsync());
}
Với việc sử dụng id trùng với cấu hình trong Startup.cs, chúng ta sẽ thay đổi URL: https://localhost:44307/books/Index/Habits

Tất nhiên chúng ta không mong muốn người dùng nhập thông tin tìm kiếm trong URL, mà chúng ta sẽ cho phép người dùng tìm kiếm thông qua một giao diện (UI). Trước khi tạo giao diện, chúng ta thay đổi phương thức Index bằng cách dùng lại tham số searchString
public async Task<IActionResult> Index(string searchString)
{
var books = from b in _context.Book
select b;
if (!String.IsNullOrEmpty(searchString))
{
books = books.Where(s => s.Title!.Contains(searchString));
}
return View(await books.ToListAsync());
}
Mở tập tin Index.cshtml từ Views/Books và thêm đoạn mã <form> (in đậm)
@model IEnumerable<MVCBooks.Models.Book>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Books" asp-action="Index">
<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
Ở đây chúng ta tạo một form dùng Form Tag Helper. Lưu và thực thi ứng dụng, nhập từ habits trong input

Nhấn nút Filter sẽ cho kết quả giống các trường hợp dùng URL ở trên. Ở đây, chúng ta không cần dùng [HttpPost] cho phương thức Index, đơn giản vì chúng ta chỉ muốn lọc thông tin có sẵn và không làm thay đổi trạng thái của ứng dụng.
Xem lại kết quả tìm kiếm và chú ý URL

URL không thay đổi khi nhấn Filter và điều này sẽ khó khăn trong trường hợp chúng ta muốn bookmark kết quả tìm kiếm hay chia sẻ kết quả cho người khác qua URL.
Chỉnh sửa hạn chế này bằng cách dùng HTTP GET khi thực hiện yêu cầu đến server thông qua việc thêm method = get đến form:
@model IEnumerable<MVCBooks.Models.Book>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Books" asp-action="Index" method="get">
<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
Thực thi lại ứng dụng, gõ habits và nhấn Filter

Lúc này URL xuất hiện tham số searchString cho phép chúng ta bookmark hay chia sẻ đến người khác.
Thêm tìm kiếm theo Genre
Thêm một lớp tên BookGenreViewModel.cs đến thư mục Models. Thay đổi nội dung lớp như sau:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MVCBooks.Models
{
public class BookGenreViewModel
{
public List<Book>? Books { get; set; }
public SelectList? Genres { get; set; }
public string? BookGenre { get; set; }
public string? SearchString { get; set; }
}
}
Lớp này chứa:
- Một danh sách các đối tượng Book
- Một SelectList chứa danh sách các Genre và cho phép người dùng chọn một genre từ danh sách
- BookGenre chứa genre được chọn
- SearchString chứa thông tin người dùng nhập từ Input Textbox
Kế tiếp, thay đổi nội dung phương thức Index trong BooksController.cs như sau:
public async Task<IActionResult> Index(string bookGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from b in _context.Book
orderby b.Genre
select b.Genre;
var books = from b in _context.Book
select b;
if (!string.IsNullOrEmpty(searchString))
{
books = books.Where(s => s.Title!.Contains(searchString));
}
if (!string.IsNullOrEmpty(bookGenre))
{
books = books.Where(x => x.Genre == bookGenre);
}
var bookGenreVM = new BookGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Books = await books.ToListAsync()
};
return View(bookGenreVM);
}
Đoạn mã đầu tiên chúng ta dùng một truy vấn LINQ để nhận toàn bộ các Genre từ cơ sở dữ liệu:
IQueryable<string> genreQuery = from b in _context.Book
orderby b.Genre
select b.Genre;
Một SelectList các Genre được tạo từ các Genre khác nhau nhận được từ cơ sở dữ liệu:
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Khi người dùng tìm kiếm, thông tin người dùng gõ sẽ được giữ lại trong ô tìm kiếm (Input Textbox).
Cuối cùng, giao diện ứng dụng hay nội dung tập tin Index.cshtml được thay đổi như sau (in đậm):
@model MVCBooks.Models.BookGenreViewModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Books" asp-action="Index" method="get">
<p>
<select asp-for="BookGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Books[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Books[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Books[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Books[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Books)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</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>
Chúng ta sử dụng DisplayNameFor là một HTML Helper kết hợp với các biểu thức lambda để tham chiếu đến giá trị các thuộc tính của đối tượng Book bao gồm Title, ReleaseDate, Genre và Price.
Chạy ứng dụng. Kiểm tra tìm kiếm thông tin Book theo Title, theo Genre hay cả hai.