添加验证

作者 :Rick Anderson

注意

此处使用最新版本的 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 提供了一组内置的验证属性,你可以以声明方式将这些属性应用于任何类或属性。 (它还包含有助于格式化且不提供任何 validation 的 DataType 等格式属性。)

现在,更新 Movie 类以利用内置 RequiredStringLength、RegularExpressionRange验证属性。 将 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。 on 的最小长度为 3,on Title 的范围 Price 未创建架构更改。

检查电影架构:

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

字符串字段显示新的长度限制,并且 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 操作方法测试验证后,即已应用相同的验证。

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

如何在创建视图和创建操作方法中发生验证

你可能想知道在不对控制器或视图中的代码进行任何更新的情况下,验证 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如何使用帮助程序输出每个Movie属性的 <input> 元素。 此帮助程序旁边是对帮助程序方法的 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 方法进行一些改进。