使用英语阅读

通过


添加验证

作者: 里克·安德森

备注

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

本教程介绍具有控制器和视图的 ASP.NET Core MVC。 Razor Pages 是 ASP.NET Core 中的一种新替代方法,它是一种基于页面的编程模型,使生成 Web UI 更加轻松高效。 建议先尝试 Razor 页面教程,再使用 MVC 版本。 Razor 页面教程:

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

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

使事情保持干燥

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

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

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

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

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

打开 Movie.cs 文件。 请注意命名空间 System.ComponentModel.DataAnnotations 不包含 System.Web。 DataAnnotations 提供一组内置的验证属性,你可以以声明方式应用于任何类或属性。 (它还包含格式设置属性,如 有助于设置格式且不提供任何验证的 DataType

现在更新Movie类以利用内置RequiredStringLengthRegularExpressionRange验证属性。 将 Movie 类替换为以下代码:

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

    [StringLength(60, MinimumLength = 3)]
    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; }
  
    [RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
    [Required]
    [StringLength(30)]
    public string Genre { get; set; }

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

    [RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
    [StringLength(5)]
    public string Rating { get; set; }
}

StringLength 属性设置字符串的最大长度,并且它会对数据库设置此限制,因此数据库架构将发生更改。 右键单击服务器资源管理器中的 Movies 表,然后单击“打开表定义”:

显示“服务器资源管理器”窗口打开并在“d b o 点电影设计”选项卡上的屏幕截图。

在上图中,可以看到所有字符串字段都设置为 NVARCHAR (MAX)。 我们将使用迁移来更新架构。 生成解决方案,然后打开程序包管理器控制台窗口并输入以下命令:

add-migration DataAnnotations
update-database

此命令完成后,Visual Studio 将打开定义具有指定DataAnnotations名称的新DbMigration派生类的类文件,并在方法中Up可以看到更新架构约束的代码:

public override void Up()
{
    AlterColumn("dbo.Movies", "Title", c => c.String(maxLength: 60));
    AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false, maxLength: 30));
    AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
}

Genre 字段不再可为空(即必须输入值)。 该 Rating 字段的最大长度为 5, Title 最大长度为 60。 最小长度为 3, Title 范围 Price 未创建架构更改。

检查电影架构:

显示“d b o 点电影设计”选项卡的屏幕截图。标题和分级在“允许 Nulls”列中选中。

字符串字段显示新的长度限制, Genre 不再显示为可为 null。

验证特性指定要对应用这些特性的模型属性强制执行的行为。 RequiredMinimumLength 特性表示属性必须有值;但用户可输入空格来满足此验证。 RegularExpression 属性用于限制可以输入的字符。 在上述代码中,GenreRating 仅可使用字母(禁用空格、数字和特殊字符)。 Range 特性将值限制在指定范围内。 StringLength 特性使你能够设置字符串属性的最大长度,以及可选的最小长度。 值类型(如 decimal, int, float, DateTime)本质上是必需的,不需要属性 Required

Code First 可确保在应用程序保存数据库中的更改之前强制执行对模型类指定的验证规则。 例如,下面的代码会在调用方法时SaveChanges引发 DbEntityValidationException 异常,因为缺少几个必需的Movie属性值:

MovieDBContext db = new MovieDBContext();
Movie movie = new Movie();
movie.Title = "Gone with the Wind";
db.Movies.Add(movie);
db.SaveChanges();        // <= Will throw server side validation exception

上面的代码引发以下异常:

一个或多个实体的验证失败。 有关更多详细信息,请参阅“EntityValidationErrors”属性。

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

ASP.NET MVC 中的验证错误 UI

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

单击“新建”链接添加新电影。 使用无效值填写表单。 当 jQuery 客户端验证检测到错误时,会显示一条错误消息。

8_validationErrors

备注

若要支持对使用逗号(“,”)的非英语区域设置进行 jQuery 验证(对于小数点),必须包括 NuGet 全球化,如本教程前面所述。

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

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

存在客户端验证错误时,不会将表单数据发送到服务器。 可以通过使用 fiddler 工具或 IE F12 开发人员工具将断点放在 HTTP Post 方法中来验证这一点。

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

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

public ActionResult Create()
{
    return View();
}
// POST: /Movies/Create
// 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 Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] 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,则禁用客户端验证,HTTP POST Create 方法将检查 ModelState.IsValid 电影是否有任何验证错误。

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

显示“Internet 选项”窗口打开并在“安全”选项卡上的屏幕截图。“自定义级别”窗口处于打开状态并禁用活动脚本。

显示 H t t p 帖子以及模型状态点是否有效突出显示的屏幕截图。

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

显示“选项”窗口的屏幕截图。已选择内容,“启用 Java 脚本”将圆圈为红色。

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

显示 Java 脚本设置和允许或禁用选项的屏幕截图。

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

@model MvcMovie.Models.Movie
@{
    ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        <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>
        @*Fields removed for brevity.*@        




        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

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

此方法真正好的一点是:无论是控制器还是 Create 视图模板都不知道强制实施的实际验证规则或显示的特定错误消息。 仅可在 Movie 类中指定验证规则和错误字符串。 这些相同的验证规则自动应用于 Edit 视图和可能创建用于编辑模型的任何其他视图模板。

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

使用 DataType 特性

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

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

DataType 属性仅提供视图引擎设置数据格式的提示(并提供 URL 和<a href="mailto:EmailAddress.com">电子邮件等<a>属性)。 可以使用 RegularExpression 属性来验证数据的格式。 DataType 属性用于指定比数据库内部类型更具体的数据类型,它们不是验证属性。 在此示例中,我们只想跟踪日期,而不是日期和时间。 DataType 枚举为许多数据类型提供,例如 Date、Time、PhoneNumber、Currency、EmailAddress 等。 应用程序还可通过 DataType 特性自动提供类型特定的功能。 例如,mailto:可以为 DataType.EmailAddress 创建链接,并且可为支持 HTML5 的浏览器中的 DataType.Date 提供日期选择器。 DataType 属性发出 HTML 5 数据(发音的数据短划线)属性,HTML 5 浏览器可以理解这些属性。 DataType 属性不提供任何验证。

DataType.Date 不指定显示日期的格式。 默认情况下,数据字段根据服务器 CultureInfo 的默认格式显示。

DisplayFormat 特性用于显式指定日期格式:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

ApplyFormatInEditMode 设置指定在文本框中显示值以供编辑时,还应应用指定的格式。 (你可能不希望对某些字段使用,例如,对于货币值,你可能不希望文本框中的货币符号进行编辑。

可以单独使用 DisplayFormat 属性,但通常最好也使用 DataType 属性。 该 DataType 属性传达 数据的语义 ,而不是如何在屏幕上呈现数据,并提供以下好处,你无法获取 DisplayFormat

  • 浏览器可以启用 HTML5 功能(例如显示日历控件、适合区域设置的货币符号、电子邮件链接等)。
  • 默认情况下,浏览器将根据区域设置使用正确的格式呈现数据。
  • DataType 属性可以让 MVC 选择正确的字段模板来呈现数据(如果由自身使用字符串模板,则 DisplayFormat)。 有关详细信息,请参阅 Brad Wilson 的 ASP.NET MVC 2 模板。 (虽然为 MVC 2 编写,但本文仍适用于当前版本的 ASP.NET MVC。

如果将 DataType 属性与日期字段一起使用,则还必须指定 DisplayFormat 该属性,以确保该字段在 Chrome 浏览器中正确呈现。 有关详细信息,请参阅 此 StackOverflow 线程

备注

jQuery 验证不适用于 Range 属性和 DateTime。 例如,以下代码将始终显示客户端验证错误,即便日期在指定的范围内:

[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

需要禁用 jQuery 日期验证才能将 Range 属性与 DateTime 一起使用。 在模型中编译硬日期通常不是很好的做法,因此不建议使用 Range 属性和 DateTime

以下代码显示组合在一行上的特性:

public class Movie
{
   public int ID { get; set; }
   [Required,StringLength(60, MinimumLength = 3)]
   public string Title { get; set; }
   [Display(Name = "Release Date"),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; }
   [Required,StringLength(5)]
   public string Rating { get; set; }
}

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