检查电影控制器的编辑操作方法和视图

作者 :Rick Anderson

注意

此处使用最新版本的 Visual Studio 提供了本教程的更新版本。 新教程使用 ASP.NET Core MVC,这比本教程提供了许多改进。

本教程介绍具有控制器和视图的 ASP.NET Core MVC。 Razor Pages 是 ASP.NET Core 中的一个新替代项,它是一种基于页面的编程模型,可简化 Web UI 的生成并提高效率。 建议先尝试 Razor 页面教程,再使用 MVC 版本。 Razor 页面教程:

  • 易于关注。
  • 涵盖更多功能。
  • 是新应用开发的首选方法。

在本部分中,你将检查电影控制器生成的 Edit 操作方法和视图。 但首先,我们将进行简短的改用,使发布日期看起来更好。 打开 Models\Movie.cs 文件并添加突出显示的行,如下所示:

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

还可以使日期区域性具体化,如下所示:

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }

我们将在下一教程中介绍 DataAnnotationsDisplay 特性指定要显示的字段名称的内容(本例中应为“Release Date”,而不是“ReleaseDate”)。 DataType 属性指定数据类型,在本例中为日期,因此不会显示存储在字段中的时间信息。 Chrome 浏览器中错误呈现日期格式的 bug 需要 DisplayFormat 属性。

运行应用程序并浏览到 Movies 控制器。 将鼠标指针悬停在 “编辑” 链接上,以查看它链接到的 URL。

EditLink_sm

Edit 链接是由 Html.ActionLinkViews\Movies\Index.cshtml 视图中的 方法生成的:

@Html.ActionLink("Edit", "Edit", new { id=item.ID })

Html.ActionLink

对象 Html 是使用 System.Web.Mvc.WebViewPage 基类上的 属性公开的帮助程序。 借助 ActionLink 帮助程序的方法,可以轻松地动态生成链接到控制器上的操作方法的 HTML 超链接。 方法的第一个参数 ActionLink 是要呈现 (的链接文本, <a>Edit Me</a> 例如,) 。 第二个参数是要调用的操作方法的名称 (在这种情况下, Edit 操作) 。 最后一个参数是一个 匿名对象 ,该对象生成路由数据 (在本例中为 4) ID。

上图中显示的生成的链接为 http://localhost:1234/Movies/Edit/4。 在 App_Start\RouteConfig.cs) 中建立的默认路由 (采用 URL 模式 {controller}/{action}/{id}。 因此,ASP.NET 转换为http://localhost:1234/Movies/Edit/4Edit参数ID等于 4 的Movies控制器的操作方法的请求。 检查 App_Start\RouteConfig.cs 文件中的以下代码。 MapRoute 方法用于将 HTTP 请求路由到正确的控制器和操作方法,并提供可选的 ID 参数。 MapRoute 方法也由 HtmlHelpers 使用,例如ActionLink在给定控制器、操作方法和任何路由数据的情况下生成 URL。

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:1234/Movies/Edit?ID=3 还会将 参数 ID 3 传递给 Edit 控制器的操作 Movies 方法。

EditQueryString

打开 Movies 控制器。 下面显示了这两 Edit 种操作方法。

// GET: /Movies/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

// POST: /Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

请注意第二个 Edit 操作方法的前面是 HttpPost 特性。 此属性指定只能对 POST 请求调用 方法的 Edit 重载。 可以将 属性 HttpGet 应用于第一个 edit 方法,但这不是必需的,因为它是默认值。 (我们将隐式分配 HttpGet 属性的操作方法称为 HttpGet methods。) Bind 属性是防止黑客将数据过度发布到模型的另一个重要安全机制。 应仅在绑定属性中包含要更改的属性。 可以在我的过度发布安全说明中阅读有关 过度发布和 bind 属性的信息。 在本教程中使用的简单模型中,我们将绑定模型中的所有数据。 ValidateAntiForgeryToken 属性用于防止伪造请求,并在@Html.AntiForgeryToken()编辑视图文件中与 配对 (Views\Movies\Edit.cshtml) ,下面显示了一部分:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

@Html.AntiForgeryToken() 生成在控制器的 方法中 Edit 必须匹配的 Movies 隐藏形式防伪令牌。 可以在 MVC 中的 XSRF/CSRF 防护教程中阅读有关跨站点请求伪造 (也称为 XSRF 或 CSRF) 的详细信息。

方法 HttpGetEdit 采用电影 ID 参数,使用 Entity Framework Find 方法查找电影,并将所选电影返回到“编辑”视图。 如果找不到电影,则返回 HttpNotFound 。 当基架系统创建“编辑”视图时,它会检查 Movie 类并创建代码为类的每个属性呈现 <label><input> 元素。 以下示例显示由 Visual Studio 基架系统生成的“编辑”视图:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ReleaseDate)
                @Html.ValidationMessageFor(model => model.ReleaseDate)
            </div>
        </div>
        @*Genre and Price removed for brevity.*@        
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

请注意,视图模板在文件顶部有 一个 @model MvcMovie.Models.Movie 语句 , 这指定视图要求视图模板的模型的类型 Movie为 。

基架代码使用多种 帮助程序方法来 简化 HTML 标记。 帮助 Html.LabelFor 程序显示字段的名称 (“Title”、“ReleaseDate”、“Genre”或“Price”) 。 帮助 Html.EditorFor 程序呈现 HTML <input> 元素。 帮助 Html.ValidationMessageFor 程序显示与该属性关联的任何验证消息。

运行应用程序并导航到 /Movies URL。 点击“编辑”链接。 在浏览器中查看页面的源。 下面显示了表单元素的 HTML。

<form action="/movies/Edit/4" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <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="control-group">
         <label class="control-label" for="Title">Title</label>
         <div class="controls">
            <input class="text-box single-line" id="Title" name="Title" type="text" value="GhostBusters" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Title" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="ReleaseDate">Release Date</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-date="The field Release Date must be a date." data-val-required="The Release Date field is required." id="ReleaseDate" name="ReleaseDate" type="date" value="1/1/1984" />
            <span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Genre">Genre</label>
         <div class="controls">
            <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Comedy" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Price">Price</label>
         <div class="controls">
            <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="7.99" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Price" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="form-actions no-color">
         <input type="submit" value="Save" class="btn" />
      </div>
   </fieldset>
</form>

元素 <input> 位于 HTML <form> 元素中,其 action 属性设置为发布到 /Movies/Edit URL。 单击“ 保存” 按钮时,表单数据将发布到服务器。 第二行显示调用生成的@Html.AntiForgeryToken()隐藏 XSRF 令牌。

处理 POST 请求

以下列表显示了 Edit 操作方法的 HttpPost 版本。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

ValidateAntiForgeryToken 属性验证视图中的调用生成的 @Html.AntiForgeryToken()XSRF 令牌。

ASP.NET MVC 模型联编程序采用已发布的表单值,并创建作为 Moviemovie 参数传递的对象。 验证 ModelState.IsValid 表单中提交的数据是否可用于修改 (编辑或更新) Movie 对象。 如果数据有效,电影数据将保存到Movies (MovieDBContext实例的集合db) 。 通过调用 SaveChanges 的 方法 MovieDBContext将新的电影数据保存到数据库。 保存数据后,代码将用户重定向到 MoviesController 类的 Index 操作方法,此方法显示电影集合,包括刚才所做的更改。

一旦客户端验证确定字段的值无效,就会显示一条错误消息。 如果 JavaScript 处于禁用状态,则禁用客户端验证。 但是,服务器检测到发布的值无效,并且表单值会重新显示错误消息。

本教程稍后会更详细地介绍验证。

Html.ValidationMessageForEdit.cshtml 视图模板中的帮助程序负责显示相应的错误消息。

abcNotValid

HttpGet所有方法都遵循类似的模式。 在) 的情况下,它们获取 (或对象列表的 Index 电影对象,并将模型传递给视图。 方法 Create 将空电影对象传递给“创建”视图。 在方法的 HttpPost 重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 修改 HTTP GET 方法中的数据存在安全风险,如博客文章 文章 ASP.NET MVC 提示 #46 - 请勿使用删除链接,因为它们会创建安全漏洞。 修改 GET 方法中的数据也违反了 HTTP 最佳做法和体系结构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。

非英语区域设置的 jQuery 验证

如果使用US-English计算机,可以跳过此部分并转到下一教程。 可以 在此处下载本教程的全球化版本。 有关国际化的优秀两部分教程,请参阅 Nadeem 的 ASP.NET MVC 5 国际化

注意

若要支持使用逗号 (的非英语区域设置的 jQuery 验证“、”) 小数点和非US-English日期格式“,必须包含来自 https://github.com/jquery/globalizeglobalize.js) 和 JavaScript 的特定区域性/globalize.cultures.js文件 (,才能使用 Globalize.parseFloat。 可以从 NuGet 获取 jQuery 非英语验证。 (如果使用英语 locale.)

  1. “工具 ”菜单中,单击“ NuGet 包管理器”,然后单击“ 管理解决方案的 NuGet 包”。

    用于开始非英语区域设置的 jQuery 验证的“工具”菜单的屏幕截图。

  2. 在左窗格中,选择“ 浏览”*。* (见下图。)

  3. 在输入框中,输入 Globalize*。

    用于输入“全球化”的输入框的屏幕截图。

    选择 jQuery.Validation.Globalize,选择 MvcMovie 并单击“ 安装”。 Scripts\jquery.globalize\globalize.js文件将添加到项目中。 *Scripts\jquery.globalize\cultures* 文件夹将包含许多区域性 JavaScript 文件。 请注意,安装此包可能需要五分钟。

    以下代码显示了对 Views\Movies\Edit.cshtml 文件的修改:

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

<script src="~/Scripts/globalize/globalize.js"></script>
<script src="~/Scripts/globalize/cultures/globalize.culture.@(System.Threading.Thread.CurrentThread.CurrentCulture.Name).js"></script>
<script>
    $.validator.methods.number = function (value, element) {
        return this.optional(element) ||
            !isNaN(Globalize.parseFloat(value));
    }
    $(document).ready(function () {
        Globalize.culture('@(System.Threading.Thread.CurrentThread.CurrentCulture.Name)');
    });
</script>
<script>
    jQuery.extend(jQuery.validator.methods, {
        range: function (value, element, param) {
            //Use the Globalization plugin to parse the value
            var val = Globalize.parseFloat(value);
            return this.optional(element) || (
                val >= param[0] && val <= param[1]);
        }
    });
    $.validator.methods.date = function (value, element) {
        return this.optional(element) ||
            Globalize.parseDate(value) ||
            Globalize.parseDate(value, "yyyy-MM-dd");
    }
</script>
}

为了避免在每个“编辑”视图中重复此代码,可以将它移动到布局文件。 若要优化脚本下载,请参阅我的教程 捆绑和缩小

有关详细信息 ,请参阅 ASP.NET MVC 3 国际化ASP.NET MVC 3 国际化 - 第 2 部分 (NerdDinner)

临时解决方法是,如果无法在区域设置中获取验证,则可以强制计算机使用美国英语,也可以在浏览器中禁用 JavaScript。 若要强制计算机使用美国英语,可以将全球化元素添加到项目根 web.config 文件。 以下代码显示了全球化元素,区域性设置为英语美国。

<system.web>
    <globalization culture ="en-US" />
    <!--elements removed for clarity-->
  </system.web>

在下一教程中,我们将实现搜索功能。