将搜索添加到 ASP.NET Core MVC 应用
在本部分中,将向 Index
操作方法添加搜索功能,以实现按“类型”或“名称”搜索电影 。
使用以下代码更新 Controllers/MoviesController.cs 中的 Index
方法:
public async Task<IActionResult> Index(string searchString) { var movies = from m in _context.Movie select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } return View(await movies.ToListAsync()); }
Index
操作方法的第一行创建了 LINQ 查询用于选择电影:
var movies = from m in _context.Movie select m;
此时仅对查询进行了定义,它还不会针对数据库运行 。
如果 searchString
参数包含一个字符串,电影查询则会被修改为根据搜索字符串的值进行筛选:
if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); }
上面的 s => s.Title.Contains()
代码是 Lambda 表达式。 Lambda 在基于方法的 LINQ 查询中用作标准查询运算符方法的参数,如 Where 方法或 Contains
(上述的代码中所使用的)。 在对 LINQ 查询进行定义或通过调用方法(如 Where
、Contains
或 OrderBy
)进行修改后,此查询不会被执行。 相反,会延迟执行查询。 这意味着表达式的计算会延迟,直到真正循环访问其实现的值或者调用 ToListAsync
方法为止。 有关延迟执行查询的详细信息,请参阅Query Execution(查询执行)。
注意:Contains 方法在数据库上运行,而不是在上面显示的 C# 代码中运行。 查询是否区分大小写取决于数据库和排序规则。 在 SQL Server 上,Contains 映射到 SQL LIKE,这是不区分大小写的。 在 SQLite 中,由于使用了默认排序规则,因此需要区分大小写。
导航到 /Movies/Index
。 将查询字符串(如 ?searchString=Ghost
)追加到 URL。 筛选的电影将显示出来。
如果将 Index
方法的签名更改为具有名称为 id
的参数,则 id
参数将匹配 Startup.cs 中设置的默认路由的可选 {id}
占位符 。
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
将参数更改为 id
,并将出现的所有 searchString
更改为 id
。
之前的 Index
方法:
public async Task<IActionResult> Index(string searchString) { var movies = from m in _context.Movie select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } return View(await movies.ToListAsync()); }
更新后带 id
参数的 Index
方法:
public async Task<IActionResult> Index(string id) { var movies = from m in _context.Movie select m; if (!String.IsNullOrEmpty(id)) { movies = movies.Where(s => s.Title.Contains(id)); } return View(await movies.ToListAsync()); }
现可将搜索标题作为路由数据( URL 段)而非查询字符串值进行传递。
但是,不能指望用户在每次要搜索电影时都修改 URL。 因此需要添加 UI 元素来帮助他们筛选电影。 若已更改 Index
方法的签名,以测试如何传递绑定路由的 ID
参数,请改回原样,使其采用名为 searchString
的参数:
public async Task<IActionResult> Index(string searchString) { var movies = from m in _context.Movie select m; if (!String.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } return View(await movies.ToListAsync()); }
打开“Views/Movies/Index.cshtml”文件,并添加以下突出显示的 <form>
标记 :
ViewData["Title"] = "Index"; } <h2>Index</h2> <p> <a asp-action="Create">Create New</a> </p> <form asp-controller="Movies" asp-action="Index"> <p> Title: <input type="text" name="SearchString"> <input type="submit" value="Filter" /> </p> </form> <table class="table"> <thead>
此 HTML <form>
标记使用表单标记帮助程序,因此提交表单时,筛选器字符串会发布到电影控制器的 Index
操作。 保存更改,然后测试筛选器。
如你所料,不存在 Index
方法的 [HttpPost]
重载。 无需重载,因为该方法不更改应用的状态,仅筛选数据。
可添加以下 [HttpPost] Index
方法。
[HttpPost] public string Index(string searchString, bool notUsed) { return "From [HttpPost]Index: filter on " + searchString; }
notUsed
参数用于创建 Index
方法的重载。 本教程稍后将对此进行探讨。
如果添加此方法,则操作调用程序将与 [HttpPost] Index
方法匹配,且将运行 [HttpPost] Index
方法,如下图所示。
但是,即使添加 Index
方法的 [HttpPost]
版本,其实现方式也受到限制。 假设你想要将特定搜索加入书签,或向朋友发送一个链接,让他们单击链接即可查看筛选出的相同电影列表。 请注意 HTTP POST 请求的 URL 与 GET 请求的 URL (localhost:{PORT}/Movies/Index) 相同,URL 中没有任何搜索信息。 搜索字符串信息作为表单域值发送给服务器。 可使用浏览器开发人员工具或出色的 Fiddler 工具对其进行验证。 下图展示了 Chrome 浏览器开发人员工具:
在请求正文中,可看到搜索参数和 XSRF 标记。 请注意,正如之前教程所述,表单标记帮助程序 会生成一个 XSRF 防伪标记。 不会修改数据,因此无需验证控制器方法中的标记。
搜索参数位于请求正文而非 URL 中,因此无法捕获该搜索信息进行书签设定或与他人共享。 修复方法是指定:请求应为 Views/Movies/Index.cshtml 文件中的 HTTP GET
。
@model IEnumerable<MvcMovie.Models.Movie> @{ ViewData["Title"] = "Index"; } <h1>Index</h1> <p> <a asp-action="Create">Create New</a> </p> <form asp-controller="Movies" 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> @Html.DisplayNameFor(model => model.Title)
现在提交搜索后,URL 将包含搜索查询字符串。 即使具备 HttpPost Index
方法,搜索也将转到 HttpGet Index
操作方法。
以下标记显示对 form
标记的更改:
<form asp-controller="Movies" asp-action="Index" method="get">
添加按流派搜索
将以下 MovieGenreViewModel
类添加到“模型”文件夹 :
using Microsoft.AspNetCore.Mvc.Rendering; using System.Collections.Generic; namespace MvcMovie.Models { public class MovieGenreViewModel { public List<Movie> Movies { get; set; } public SelectList Genres { get; set; } public string MovieGenre { get; set; } public string SearchString { get; set; } } }
“电影流派”视图模型将包含:
- 电影列表。
- 包含流派列表的
SelectList
。 这使用户能够从列表中选择一种流派。 - 包含所选流派的
MovieGenre
。 SearchString
包含用户在搜索文本框中输入的文本。
将 MoviesController.cs
中的 Index
方法替换为以下代码:
// GET: Movies public async Task<IActionResult> Index(string movieGenre, string searchString) { // Use LINQ to get list of genres. IQueryable<string> genreQuery = from m in _context.Movie orderby m.Genre select m.Genre; var movies = from m in _context.Movie select m; if (!string.IsNullOrEmpty(searchString)) { movies = movies.Where(s => s.Title.Contains(searchString)); } if (!string.IsNullOrEmpty(movieGenre)) { movies = movies.Where(x => x.Genre == movieGenre); } var movieGenreVM = new MovieGenreViewModel { Genres = new SelectList(await genreQuery.Distinct().ToListAsync()), Movies = await movies.ToListAsync() }; return View(movieGenreVM); }
以下代码是一种 LINQ
查询,可从数据库中检索所有流派。
// Use LINQ to get list of genres. IQueryable<string> genreQuery = from m in _context.Movie orderby m.Genre select m.Genre;
通过投影不同的流派创建 SelectList
(我们不希望选择列表中的流派重复)。
当用户搜索某个项目时,搜索值会保留在搜索框中。
向“索引”视图添加“按流派搜索”
按如下所示更新 Views/Movies/ 中的 Index.cshtml
:
@model MvcMovie.Models.MovieGenreViewModel @{ ViewData["Title"] = "Index"; } <h1>Index</h1> <p> <a asp-action="Create">Create New</a> </p> <form asp-controller="Movies" asp-action="Index" method="get"> <p> <select asp-for="MovieGenre" asp-items="Model.Genres"> <option value="">All</option> </select> Title: <input type="text" asp-for="SearchString" /> <input type="submit" value="Filter" /> </p> </form> <table class="table"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.Movies[0].Title) </th> <th> @Html.DisplayNameFor(model => model.Movies[0].ReleaseDate) </th> <th> @Html.DisplayNameFor(model => model.Movies[0].Genre) </th> <th> @Html.DisplayNameFor(model => model.Movies[0].Price) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model.Movies) { <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>
检查以下 HTML 帮助程序中使用的 Lambda 表达式:
@Html.DisplayNameFor(model => model.Movies[0].Title)
在上述代码中,DisplayNameFor
HTML 帮助程序检查 Lambda 表达式中引用的 Title
属性来确定显示名称。 由于只检查但未计算 Lambda 表达式,因此当 model
、model.Movies[0]
或 model.Movies
为 null
或空时,你不会收到访问冲突。 对 Lambda 表达式求值时(例如,@Html.DisplayFor(modelItem => item.Title)
),将求得该模型的属性值。
通过按流派搜索、按电影标题搜索以及按流派和电影标题搜索来测试应用: