将验证逻辑添加到电影模型

作者: 里克·安德森

注意

此处提供了本教程的更新版本,它使用 ASP.NET MVC 5 和 Visual Studio 2013。 它更安全,更易于遵循并演示更多功能。

在本部分中,你将向模型添加验证逻辑 Movie ,并确保每当用户尝试使用应用程序创建或编辑电影时,都会强制实施验证规则。

使事情保持干燥

ASP.NET MVC 的核心设计原则之一是 DRY(“不要重复自己”)。 ASP.NET MVC 鼓励你仅指定一次功能或行为,然后将其反映在应用程序中的任何地方。 这减少了编写所需的代码量,并使你编写的代码容易出错且更易于维护。

ASP.NET MVC 和 Entity Framework Code First 提供的验证支持是 DRY 原则在操作中的一个很好的示例。 可以在一个位置(在模型类中)声明性地指定验证规则,并在应用程序中的任何地方强制实施这些规则。

让我们看看如何在电影应用程序中利用此验证支持。

将验证规则添加到电影模型

首先,将一些验证逻辑添加到 Movie 类。

打开 Movie.cs 文件。 在引用System.ComponentModel.DataAnnotations命名空间的文件顶部添加语句using

using System.ComponentModel.DataAnnotations;

请注意命名空间不包含 System.Web。 DataAnnotations 提供一组内置的验证属性,你可以以声明方式应用于任何类或属性。

现在更新Movie类以利用内置RequiredStringLength属性和Range验证属性。 使用以下代码作为应用属性的位置的示例。

public class Movie {
    public int ID { get; set; }

    [Required]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Required]
    public string Genre { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

运行应用程序,你将再次收到以下运行时错误:

自创建数据库以来,支持“MovieDBContext”上下文的模型发生了更改。请考虑使用Code First 迁移来更新数据库(https://go.microsoft.com/fwlink/?LinkId=238269)。

我们将使用迁移来更新架构。 生成解决方案,然后打开程序包管理器控制台窗口并输入以下命令:

add-migration AddDataAnnotationsMig
update-database

此命令完成后,Visual Studio 将打开类文件,该文件定义 DbMigration 具有指定名称的新派生类(AddDataAnnotationsMig),并在方法中 Up 可以看到更新架构约束的代码。 字段TitleGenre不再可为 null(即,必须输入值),Rating并且该字段的最大长度为 5。

验证特性指定要对应用这些特性的模型属性强制执行的行为。 该Required属性指示属性必须具有值;在此示例中,电影必须具有值GenreTitleReleaseDatePrice才能有效。 Range 特性将值限制在指定范围内。 StringLength 特性使你能够设置字符串属性的最大长度,以及可选的最小长度。 默认情况下,内部类型(如 decimal, int, float, DateTime)是必需的,不需要属性 Required

Code First 可确保在应用程序保存数据库中的更改之前强制执行对模型类指定的验证规则。 例如,下面的代码将在调用方法时 SaveChanges 引发异常,因为缺少几个必需的 Movie 属性值,并且价格为零(超出有效范围)。

MovieDBContext db = new MovieDBContext();

Movie movie = new Movie();
movie.Title = "Gone with the Wind";
movie.Price = 0.0M;

db.Movies.Add(movie);  
db.SaveChanges();        // <= Will throw server side validation exception  

通过 .NET Framework 自动强制实施验证规则有助于使应用程序更加可靠。 同时它能确保你无法忘记验证某些内容,并防止你无意中将错误数据导入数据库。

下面是更新 Movie.cs 文件的完整代码列表:

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

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

        [Required]
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Required]
        public string Genre { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        public decimal Price { get; set; }

        [StringLength(5)]
        public string Rating { get; set; }
    }

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

ASP.NET MVC 中的验证错误 UI

重新运行应用程序并导航到 /Movies URL。

单击“新建”链接添加新电影。 使用一些无效值填写表单,然后单击“ 创建 ”按钮。

8_validationErrors

注意

若要支持对使用逗号(“,”)的非英语区域设置的 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>
}

请注意,表单如何自动使用红色边框颜色突出显示包含无效数据的文本框,并在每个文本框旁边发出了相应的验证错误消息。 客户端(使用 JavaScript 和 jQuery)和服务器端(若用户禁用 JavaScript)都必定会遇到这些错误。

真正的好处是,无需更改类或 Create.cshtml 视图中的单个代码MoviesController行才能启用此验证 UI。 在本教程前面创建的控制器和视图会自动选取验证规则,这些规则是通过在 Movie 模型类的属性上使用验证特性所指定的。

你可能已经注意到属性 Title ,并且 Genre,在提交表单(点击 “创建 ”按钮)或输入文本并将其删除之前,才会强制实施所需的属性。 对于最初为空(例如“创建”视图上的字段)且只有所需属性且没有其他验证属性的字段,可以执行以下操作来触发验证:

  1. 按 Tab 键进入字段。
  2. 输入一些文本。
  3. 退出选项卡。
  4. Tab 返回字段。
  5. 删除文本。
  6. 退出选项卡。

上述序列将触发所需的验证,而无需点击“提交”按钮。 只需点击提交按钮而不输入任何字段将触发客户端验证。 存在客户端验证错误时,不会将表单数据发送到服务器。 可以通过将断点放在 HTTP Post 方法中或使用 fiddler 工具 或 IE 9 F12 开发人员工具来测试此问题。

显示“M V C 电影创建”页面的屏幕截图。标题旁边的警报指出“标题”字段是必需的。流派旁边的警报指出“流派”字段是必需的。

在“创建视图”和“创建操作”方法中如何进行验证

你可能想知道在不对控制器或视图中的代码进行任何更新的情况下,验证 UI 是如何生成的。 下一个列表显示 Create 类中 MovieController 的方法的外观。 与在本教程前面创建它们的方式没有变化。

//
// GET: /Movies/Create

public ActionResult Create()
{
    return View();
}

//
// POST: /Movies/Create

[HttpPost]
public ActionResult Create(Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Movies.Add(movie);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(movie);
}

第一个 (HTTP GET) Create 操作方法显示初始的“创建”表单。 第二个 ([HttpPost]) 版本处理表单发布。 第二个 Create 方法(HttpPost 版本)调用 ModelState.IsValid 以检查电影是否有任何验证错误。 调用此方法将评估已应用于对象的任何验证特性。 如果对象有验证错误,则 Create 方法会重新显示此表单。 如果没有错误,此方法则将新电影保存在数据库中。 在我们的电影示例中,当客户端检测到验证错误时,表单不会发布到服务器;从不调用第二Create种方法。 如果在浏览器中禁用 JavaScript,则会禁用客户端验证,并调用 ModelState.IsValid HTTP POST Create 方法来检查电影是否有任何验证错误。

可以在 HttpPost Create 方法中设置断点,并验证方法从未被调用,客户端验证在检测到存在验证错误时不会提交表单数据。 如果在浏览器中禁用 JavaScript,然后提交错误的表单,将触发断点。 在没有 JavaScript 的情况下仍然可以进行完整的验证。 下图显示了如何在 Internet Explorer 中禁用 JavaScript。

显示“Internet 选项”窗口打开到“安全”选项卡的屏幕截图。自定义级别以红色圆圈。在“安全设置”窗口中,活动脚本设置为禁用。滚动条以红色圆圈。

显示 H t t p 帖子的屏幕截图。如果“模型状态点有效”突出显示。

以下图片显示如何在 FireFox 浏览器中禁用 JavaScript。

显示“选项”窗口的屏幕截图。已选择内容并选中“启用 Java 脚本”。

下图显示了如何使用 Chrome 浏览器禁用 JavaScript。

显示“选项”页的屏幕截图。在胡子下被选中并用红色圆圈。在内容设置中,Java 脚本设置为“允许所有站点运行 Java 脚本”。

下面是在本教程前面搭建基架的 Create.cshtml 视图模板。 以上所示的操作方法使用它来显示初始表单,并在发生错误时重新显示此表单。

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Movie</legend>

        <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>
        <div class="editor-label">
    @Html.LabelFor(model => model.Rating)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Rating)
    @Html.ValidationMessageFor(model => model.Rating)
</div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

请注意代码如何使用 Html.EditorFor 帮助程序为每个属性输出 <input> 元素 Movie 。 此帮助程序旁边是对帮助程序方法的 Html.ValidationMessageFor 调用。 这两个帮助程序方法适用于控制器传递给视图的模型对象(在本例中为 Movie 对象)。 它们会自动查找模型中指定的验证属性,并根据需要显示错误消息。

这种方法非常好的是,控制器和创建视图模板都不知道强制执行的实际验证规则或显示的特定错误消息。 仅可在 Movie 类中指定验证规则和错误字符串。 这些相同的验证规则会自动应用于“编辑”视图和可能创建用于编辑模型的任何其他视图模板。

如果以后要更改验证逻辑,可以通过向模型添加验证属性(在本示例中, movie 类)来完全在一个位置执行此操作。 无需担心对应用程序的不同部分所强制执行规则的方式不一致 - 所有验证逻辑都将定义在一个位置并用于整个应用程序。 这使代码非常简洁,并且更易于维护和改进。 这意味着对 DRY 原则的完全遵守。

向电影模型添加格式

打开 Movie.cs 文件并检查 Movie。 除了一组内置的验证特性,System.ComponentModel.DataAnnotations 命名空间还提供格式特性。 我们已经在发布日期和价格字段中应用了 DataType 枚举值。 以下代码显示具有适当 DisplayFormat 特性的 ReleaseDatePrice 属性。

[DataType(DataType.Date)] 
public DateTime ReleaseDate { get; set; }

[DataType(DataType.Currency)] 
public decimal Price { get; set; }

这些 DataType 属性不是验证属性,它们用于告知视图引擎如何呈现 HTML。 在上面的示例中,该 DataType.Date 属性仅将电影日期显示为日期,而不显示时间。 例如,以下 DataType 属性不验证数据的格式:

[DataType(DataType.EmailAddress)]
[DataType(DataType.PhoneNumber)]
[DataType(DataType.Url)]

上面列出的属性仅提供视图引擎设置数据格式的提示(并提供属性,例如 <> URL 属性和<用于电子邮件的 href=“mailto:EmailAddress.com”。> 可以使用 RegularExpression 属性来验证数据的格式。

可以使用特性的 DataType 替代方法,可以显式设置值 DataFormatString 。 以下代码显示了具有日期格式字符串(即“d”)的发布日期属性。 使用此选项可以指定不想在发布日期中计时。

[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime ReleaseDate { get; set; }

完整的 Movie 类如下所示。

public class Movie {
    public int ID { get; set; }

    [Required]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Required]
    public string Genre { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

运行应用程序并浏览到 Movies 控制器。 发布日期和价格格式良好。 下图显示了使用“fr-FR”作为区域性的发布日期和价格。

8_format_SM

下图显示了使用默认区域性(英语美国)显示的相同数据。

显示“M V C 电影索引”页的屏幕截图,其中列出了四部电影。

在本系列的下一部分中,我们将回顾应用程序,并对自动生成的 DetailsDelete 方法进行一些改进。