检查电影控制器的操作方法和视图
作者: 里克·安德森
注意
此处提供了本教程的更新版本,它使用 ASP.NET MVC 5 和 Visual Studio 2013。 它更安全,更易于遵循并演示更多功能。
在本部分中,你将检查电影控制器生成的操作方法和视图。 然后,你将添加自定义搜索页面。
运行应用程序并通过将 /Movies 追加到浏览器地址栏中的 URL,浏览到Movies
控制器。 将鼠标指针悬停在“编辑”链接上,以查看其链接到的 URL。
编辑链接是由 Html.ActionLink
Views\Movies\Index.cshtml 视图中的方法生成的:
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
该Html
对象是使用 System.Web.Mvc.WebViewPage 基类上的属性公开的帮助程序。 借助 ActionLink
帮助程序的方法,可以轻松地动态生成链接到控制器上的操作方法的 HTML 超链接。 方法的第一个参数 ActionLink
是要呈现的链接文本(例如, <a>Edit Me</a>
)。 第二个参数是要调用的操作方法的名称。 最后一个参数是一个 匿名对象,该对象 生成路由数据(在本例中为 4)。
上图中显示的生成的链接为 http://localhost:xxxxx/Movies/Edit/4
。 默认路由(在 App_Start\RouteConfig.cs 中建立)采用 URL 模式 {controller}/{action}/{id}
。 因此,ASP.NET 转换为http://localhost:xxxxx/Movies/Edit/4
控制器的操作方法Movies
的请求Edit
,参数ID
等于 4。 检查 App_Start\RouteConfig.cs 文件中的以下代码。
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
还可以使用查询字符串传递操作方法参数。 例如,URL http://localhost:xxxxx/Movies/Edit?ID=4
还会将 4 的参数 ID
传递给 Edit
控制器的操作 Movies
方法。
Movies
打开控制器。 下面显示了两 Edit
种操作方法。
//
// GET: /Movies/Edit/5
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movies/Edit/5
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
请注意第二个 Edit
操作方法的前面是 HttpPost
特性。 此属性指定只能对 POST 请求调用该方法的 Edit
重载。 可以将属性 HttpGet
应用于第一个编辑方法,但这不是必要的,因为它是默认值。 (我们将引用隐式分配 HttpGet
属性为 HttpGet
方法的操作方法。
该方法HttpGet
Edit
采用电影 ID 参数,使用 Entity Framework Find
方法查找电影,并将所选电影返回到“编辑”视图。 如果调用方法时没有参数,Edit
则 ID 参数将指定默认值为零。 如果找不到电影, 则返回 HttpNotFound 。 当基架系统创建“编辑”视图时,它会检查 Movie
类并创建代码为类的每个属性呈现 <label>
和 <input>
元素。 以下示例显示了生成的“编辑”视图:
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(model => model.ID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
请注意视图模板在文件顶部的 @model MvcMovie.Models.Movie
语句的方式 , 这指定视图希望视图模板的模型的类型 Movie
。
基架代码使用多个 帮助程序方法来 简化 HTML 标记。 帮助 Html.LabelFor
程序显示字段的名称(“Title”、“ReleaseDate”、“流派”或“Price”)。 帮助 Html.EditorFor
程序呈现 HTML <input>
元素。 帮助 Html.ValidationMessageFor
程序显示与该属性关联的任何验证消息。
运行应用程序并导航到 /Movies URL。 点击“编辑”链接。 在浏览器中查看页面的源。 窗体元素的 HTML 如下所示。
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-date="The field ReleaseDate must be a date." data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" type="text" value="2.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
元素 <input>
位于 HTML 元素中 <form>
,其 action
属性设置为发布到 /Movies/Edit URL。 单击“编辑”按钮时,表单数据将发布到服务器。
处理 POST 请求
以下列表显示了 Edit
操作方法的 HttpPost
版本。
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
ASP.NET MVC 模型绑定器采用已发布的表单值,并创建Movie
作为movie
参数传递的对象。 ModelState.IsValid
方法验证表单中提交的数据是否可以用于修改(编辑或更新)Movie
对象。 如果数据有效,电影数据将 Movies
保存到实例的 db(MovieDBContext
集合中。 新的电影数据通过调用 SaveChanges
方法 MovieDBContext
保存到数据库。 保存数据后,代码会将用户重定向到 Index
类的操作 MoviesController
方法,该方法显示电影集合,包括刚刚进行的更改。
如果发布的值无效,则会在窗体中重新显示它们。 Html.ValidationMessageFor
Edit.cshtml 视图模板中的帮助程序负责显示适当的错误消息。
注意
若要支持对使用逗号(“,”)的非英语区域设置的 jQuery 验证(“,”),必须包含globalize.js和特定区域性/globalize.cultures.js文件(from https://github.com/jquery/globalize )和 JavaScript 才能使用Globalize.parseFloat
。 以下代码显示用于处理“fr-FR”区域性的 Views\Movies\Edit.cshtml 文件的修改:
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
小数字段可能需要逗号,而不是小数点。 作为临时修复,可以将全球化元素添加到项目根 web.config 文件。 以下代码显示了区域性设置为美国英语的全球化元素。
<system.web>
<globalization culture ="en-US" />
<!--elements removed for clarity-->
</system.web>
HttpGet
所有方法都遵循类似的模式。 它们获取电影对象(或对象列表(如果是), Index
并将模型传递给视图。 该方法 Create
将空电影对象传递给“创建”视图。 在方法的 HttpPost
重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 修改 HTTP GET 方法中的数据是一种安全风险。 修改 GET 方法中的数据也违反了 HTTP 最佳做法和体系结构 REST 模式,该模式指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。
添加搜索方法和搜索视图
在本部分中,你将添加一个 SearchIndex
操作方法,用于按流派或名称搜索电影。 这将使用 /Movies/SearchIndex URL 提供。 请求将显示一个 HTML 窗体,其中包含用户可以输入的输入元素,以便搜索电影。 当用户提交表单时,操作方法将获取用户发布的搜索值,并使用值搜索数据库。
显示 SearchIndex 窗体
首先将 SearchIndex
操作方法添加到现有 MoviesController
类。 该方法将返回包含 HTML 窗体的视图。 代码如下:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
方法的第一行 SearchIndex
创建以下 LINQ 查询以选择电影:
var movies = from m in db.Movies
select m;
此时定义了查询,但尚未针对数据存储运行。
searchString
如果参数包含字符串,将使用以下代码修改电影查询以筛选搜索字符串的值:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
上面的 s => s.Title
代码是 Lambda 表达式。 Lambda 在基于 方法的 LINQ 查询中用作标准查询运算符方法的参数,例如 上述代码中使用的 Where 方法。 定义 LINQ 查询或通过调用方法(如 Where
或 OrderBy
)进行修改时,不执行 LINQ 查询。 相反,查询执行延迟,这意味着表达式的计算延迟,直到实际迭代其实现的值或 ToList
调用方法。 在 SearchIndex
示例中,查询在 SearchIndex 视图中执行。 有关延迟执行查询的详细信息,请参阅Query Execution(查询执行)。
现在,你可以实现 SearchIndex
向用户显示窗体的视图。 右键单击方法内部 SearchIndex
,然后单击“ 添加视图”。 在 “添加视图 ”对话框中,指定将对象 Movie
作为其模型类传递给视图模板。 在 基架模板 列表中,选择“ 列表”,然后单击“ 添加”。
单击“添加”按钮时,将创建 Views\Movies\SearchIndex.cshtml 视图模板。 由于你在基架模板列表中选择了“列表”,Visual Studio 会在视图中自动生成一些默认标记(基架)。 基架创建了 HTML 窗体。 它检查了 Movie
类并创建了代码来呈现 <label>
类的每个属性的元素。 以下列表显示了生成的“创建”视图:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Title
</th>
<th>
ReleaseDate
</th>
<th>
Genre
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<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>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
运行应用程序并导航到 /Movies/SearchIndex。 将查询字符串(如 ?searchString=ghost
)追加到 URL。 筛选的电影将显示出来。
如果将方法的SearchIndex
签名更改为具有命名id
参数,该id
参数将与 Global.asax 文件中设置的默认路由的占位符匹配{id}
。
{controller}/{action}/{id}
原始 SearchIndex
方法如下所示::
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
修改 SearchIndex
后的方法如下所示:
public ActionResult SearchIndex(string id)
{
string searchString = id;
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
现可将搜索标题作为路由数据( URL 段)而非查询字符串值进行传递。
但是,不能指望用户在每次要搜索电影时都修改 URL。 因此,现在你将添加 UI 来帮助他们筛选电影。 如果更改了方法的 SearchIndex
签名以测试如何传递路由绑定 ID 参数,请将其更改回,以便方法 SearchIndex
采用名为 searchString
:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
打开 Views\Movies\SearchIndex.cshtml 文件,然后在后面@Html.ActionLink("Create New", "Create")
添加以下内容:
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Filter" /></p>
}
以下示例显示了 Views\Movies\SearchIndex.cshtml 文件的一部分,其中包含添加的筛选标记。
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
Html.BeginForm
帮助程序创建一个开始<form>
标记。 当Html.BeginForm
用户通过单击“筛选器”按钮提交表单时,帮助程序会导致表单发布到自己。
运行应用程序并尝试搜索电影。
方法没有 HttpPost
重载 SearchIndex
。 不需要它,因为该方法不会更改应用程序的状态,只需筛选数据。
可添加以下 HttpPost SearchIndex
方法。 在这种情况下,操作调用程序将匹配 HttpPost SearchIndex
该方法,该方法 HttpPost SearchIndex
将运行,如下图所示。
[HttpPost]
public string SearchIndex(FormCollection fc, string searchString)
{
return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>";
}
但是,即使添加 SearchIndex
方法的 HttpPost
版本,其实现方式也受到限制。 假设你想要将特定搜索加入书签,或向朋友发送一个链接,让他们单击链接即可查看筛选出的相同电影列表。 请注意,HTTP POST 请求的 URL 与 GET 请求的 URL(localhost:xxxxx/Movies/SearchIndex)的 URL 相同 - URL 本身中没有搜索信息。 现在,搜索字符串信息作为窗体字段值发送到服务器。 这意味着无法捕获该搜索信息以书签或发送到 URL 中的好友。
解决方案是使用重载,该重载 BeginForm
指定 POST 请求应将搜索信息添加到 URL,并将其路由到方法的 SearchIndex
HttpGet 版本。 将现有无 BeginForm
参数方法替换为以下内容:
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))
现在,提交搜索时,URL 包含搜索查询字符串。 即使具备 HttpPost SearchIndex
方法,搜索也将转到 HttpGet SearchIndex
操作方法。
按流派添加搜索
如果添加了 HttpPost
该方法的版本 SearchIndex
,请立即将其删除。
接下来,你将添加一项功能,让用户按流派搜索电影。 将 SearchIndex
方法替换为以下代码:
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
此方法的 SearchIndex
此版本采用其他参数,即 movieGenre
。 前几行代码创建一个 List
对象来保存数据库中的电影流派。
下面的代码是一种 LINQ 查询,可从数据库中检索所有流派。
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
该代码使用 AddRange
泛型 List
集合的方法将所有不同的流派添加到列表中。 (如果没有 Distinct
修饰符,将添加重复的流派 - 例如,将在我们的示例中添加两次喜剧)。 然后,代码将流派列表存储在对象中 ViewBag
。
以下代码演示如何检查 movieGenre
参数。 如果不是空,则代码会进一步限制电影查询,将所选电影限制为指定的流派。
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
将标记添加到 SearchIndex 视图以支持按流派搜索
Html.DropDownList
在帮助程序之前TextBox
,将帮助程序添加到 Views\Movies\SearchIndex.cshtml 文件。 已完成的标记如下所示:
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){
<p>Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
</p>
运行应用程序并浏览到 /Movies/SearchIndex。 按流派、电影名称和两个条件尝试搜索。
在本部分中,你检查了框架生成的 CRUD 操作方法和视图。 你创建了一个搜索操作方法和视图,允许用户按电影标题和流派进行搜索。 在下一部分中,你将了解如何向模型添加属性 Movie
,以及如何添加将自动创建测试数据库的初始值设定项。