显示 / 隐藏目录

添加搜索

作者: Rick Anderson

翻译: 魏美娟(初见)

校对: 谢炀(Kiler) 、张仁建(第二年.夏) 、孟帅洋(书缘) 、高嵩

在本节中,你可以为 Index 方法添加查询功能,使其能够根据电影的 genre 或 name 进行查找。

更新 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 p参数包含一个字符串,movies 查询将会添加对应查询过滤条件( 译者注 本例为 Title 包含 searchString p查询条件),代码如下:

if (!String.IsNullOrEmpty(id))
{
    movies = movies.Where(s => s.Title.Contains(id));
}

代码中的 s => s.Title.Contains() 是一个 Lambda 表达式 ,Lambda 表达式被用在基于方法的 LINQ 查询(例如:上面代码中的 Where <http://msdn.microsoft.com/en-us/library/system.linq.enumerable.where.aspx>__ 方法 或者 Contains)中当做参数来使用。LINQ 语句在定义或调用类似 Where 、 Contains 或者 OrderBy 的方法进行修改的时候不会执行,相反的,查询会延迟执行,这意味着一个赋值语句直到迭代完成或调用 ToListAsync 方法才具备真正的值。更多关于延时查询的信息。请参考 查询执行 。

Note: Contains 方法是在数据库中运行的,并非在上面的 C# 代码中。在数据库中, Contains 方法被翻译为不区分大小写的 SQL LIKE 脚本。

运行应用程序,并导航到 /Movies/Index,在 URL 后面添加一个查询字符串,例如 ?searchString=Ghost ,被过滤后的电影列表如下:

Index view

如果你修改 Index 方法签名使得方法包含一个名为 id 的参数,那么 id 参数将会匹配 Startup.cs 文件中的默认路由中的可选项 {id} 。

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

你可以使用 rename 操作快速的把 searchString 参数重命名为 id ,在 searchString 上右击 > Rename 。

Contextual menu

会被重命名的代码会高亮显示。

Code editor showing the variable highlighted throughout the Index ActionResult method

修改参数为 id ,其他引用到 searchString 的地方也会自动变更为 id 。

Code editor showing the variable has been changed to 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 区块)来传递标题搜索条件而非查询字符串:

Index view with the word ghost added to the Url and a returned movie list of two movies, Ghostbusters and Ghostbusters 2

然而,你不能指望用户每次都通过修改URL来查找电影,因此你需要在界面上帮助他们过滤数据。如果你想要修改 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> 标签使用 Form Tag Helper 生成,所以当你提交表单的时候,过滤字符串都被传到了 movies 控制器的 Index 方法。保存你的修改并测试过滤。

Index view with the word ghost typed into the Title filter textbox

然而 Index 方法并没有你所希望的 [HttpPost]重载。你也不需要,因为方法并不会修改应用程序的状态,仅仅只是过滤数据。

你可以添加下面的 [HttpPost] Index 方法。

[HttpPost]
public string Index(string searchString, bool notUsed)
{
    return "From [HttpPost]Index: filter on " + searchString;
}

notUsed 参数用创建 Index 方法重载。我们在后续教程中会讨论。

如果你添加了这个方法。action 会调用匹配 [HttpPost] Index 的方法, [HttpPost] Index 方法运行结果如下所示。

Browser window with application response of From HttpPost Index: filter on ghost

然而,尽管添加了 [HttpPost] 版的 Index 方法,在实现的时候仍然存在一些局限性。设想你想将一个比较详细的查询添加书签,或者你想将查询结果以链接形式发送给朋友以便于你的朋友可以看到同样的过滤结果的电影,注意观察 HTTP POST 请求的时候,URL 是没有改变的(仍然是 localhost:xxxxx/Movies/Index),这个地址本身不包含查询信息。现在,查询信息是作为 表单数据 发送到服务器的,你可以通过浏览器开发者工具或者优秀的抓包工具Fiddler 工具来验证,图片展示了使用Chrome浏览器开发者工具 :

Network tab of Developer Tools in Microsoft Edge showing a request body with a searchString value of ghost

你可以在请求正文中看到查询参数和上一个教程中提到的 XSRF 令牌。 Form Tag Helper 生成 XSRF 反伪造令牌。我们没有修改数据,所以无需在控制器方法中验证令牌。

因为查询参数在请求正文中而不是 Url 里,所以你在书签里面无法保存查询参数并共享给他人,为了解决这个问题我们必须把请求指定为 HTTP GET。注意智能感知将帮我们更新标签。

注意智能感知将帮我们更新标签。

Intellisense contextual menu with method selected in the list of attributes for the form element

Intellisense contextual menu with get selected in the list of method attribute values

请注意, <form> 标签中的专有标记。这种专有标记表示的标签是由 Tag Helpers 支持的。

form tag with purple text

当你提交检索的时候,URL 包含查询条件,如果存在 HttpGet Index 方法,查询会跳转到 HttpPost Index 方法。

Browser window showing the searchString=ghost in the Url and the movies returned, Ghostbusters and Ghostbusters 2, contain the word ghost

下面的代码显示如何修改 form 标签:

<form asp-controller="Movies" asp-action="Index" method="get">

添加按照 Genre 查询

在 Models 目录添加下面的 MovieGenreViewModel 类:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models
{
    public class MovieGenreViewModel
    {
        public List<Movie> movies;
        public SelectList genres;
        public string movieGenre { get; set; }
    }
}

move-genre 视图模型包含:

  • 电影列表
  • 包含 genre 列表的 SelectList 。允许用户从列表中选择 genre 。
  • movieGenre,包含选中的 genre

用下面的代码替换 MoviesController.cs 中的 Index 方法:

// Requires using Microsoft.AspNetCore.Mvc.Rendering;
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();
    movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    movieGenreVM.movies = await movies.ToListAsync();

    return View(movieGenreVM);
}

下面代码是通过 LINQ 语句从数据库中检索所有 genre 数据。

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

SelectList 的 genres 通过 Distinct 方法投影查询创建(我们不想选择列表中出现重复的数据)。

movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync())

在 Index 视图中添加通过 genre 检索

按照下面的方式更新 Index.cshtml :

@model MvcMovie.Models.MovieGenreViewModel

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<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" name="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>

测试程序并分别通过 genre 或者电影标题以及两个条件同时进行检索

上一节 下一节

  • 改进文档
  • 0 评论
返回顶部 Copyright © 2015-2017 Microsoft
Generated by DocFX