第 9 部分,将验证添加到 ASP.NET Core MVC 应用Part 9, add validation to an ASP.NET Core MVC app

作者:Rick AndersonBy Rick Anderson

本节内容:In this section:

  • Movie 模型添加了验证逻辑。Validation logic is added to the Movie model.
  • 确保每当用户创建或编辑电影时,都会强制执行验证规则。You ensure that the validation rules are enforced any time a user creates or edits a movie.

坚持 DRY 原则Keeping things DRY

MVC 的设计原则之一是 DRY(“不要自我重复”)。One of the design tenets of MVC is DRY ("Don't Repeat Yourself"). ASP.NET Core MVC 支持你仅指定一次功能或行为,然后使它应用到整个应用中。ASP.NET Core MVC encourages you to specify functionality or behavior only once, and then have it be reflected everywhere in an app. 这可以减少所需编写的代码量,并使编写的代码更少出错,更易于测试和维护。This reduces the amount of code you need to write and makes the code you do write less error prone, easier to test, and easier to maintain.

MVC 和 Entity Framework Core Code First 提供的验证支持是 DRY 原则在实际操作中的极佳示例。The validation support provided by MVC and Entity Framework Core Code First is a good example of the DRY principle in action. 可以在一个位置(模型类中)以声明方式指定验证规则,并且在应用中的所有位置强制执行。You can declaratively specify validation rules in one place (in the model class) and the rules are enforced everywhere in the app.

将验证规则添加到电影模型Add validation rules to the movie model

DataAnnotations 命名空间提供一组内置验证特性,可通过声明方式应用于类或属性。The DataAnnotations namespace provides a set of built-in validation attributes that are applied declaratively to a class or property. DataAnnotations 还包含 DataType 等格式特性,有助于格式设置但不提供任何验证。DataAnnotations also contains formatting attributes like DataType that help with formatting and don't provide any validation.

更新 Movie 类以使用内置的 RequiredStringLengthRegularExpressionRange 验证特性。Update the Movie class to take advantage of the built-in Required, StringLength, RegularExpression, and Range validation attributes.

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

    [StringLength(60, MinimumLength = 3)]
    [Required]
    public string Title { get; set; }

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }

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

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

验证特性指定要对应用这些特性的模型属性强制执行的行为:The validation attributes specify behavior that you want to enforce on the model properties they're applied to:

  • RequiredMinimumLength 特性表示属性必须有值;但用户可输入空格来满足此验证。The Required and MinimumLength attributes indicate that a property must have a value; but nothing prevents a user from entering white space to satisfy this validation.

  • RegularExpression 特性用于限制可输入的字符。The RegularExpression attribute is used to limit what characters can be input. 在上述代码中,即“Genre”(分类):In the preceding code, "Genre":

    • 只能使用字母。Must only use letters.
    • 第一个字母必须为大写。The first letter is required to be uppercase. 不允许使用空格、数字和特殊字符。White space, numbers, and special characters are not allowed.
  • RegularExpression“Rating”(分级):The RegularExpression "Rating":

    • 要求第一个字符为大写字母。Requires that the first character be an uppercase letter.
    • 允许在后续空格中使用特殊字符和数字。Allows special characters and numbers in subsequent spaces. “PG-13”对“分级”有效,但对于“分类”无效。"PG-13" is valid for a rating, but fails for a "Genre".
  • Range 特性将值限制在指定范围内。The Range attribute constrains a value to within a specified range.

  • StringLength 特性使你能够设置字符串属性的最大长度,以及可选的最小长度。The StringLength attribute lets you set the maximum length of a string property, and optionally its minimum length.

  • 从本质上来说,需要值类型(如 decimalintfloatDateTime),但不需要 [Required] 特性。Value types (such as decimal, int, float, DateTime) are inherently required and don't need the [Required] attribute.

让 ASP.NET Core 强制自动执行验证规则有助于提升你的应用的可靠性。Having validation rules automatically enforced by ASP.NET Core helps make your app more robust. 同时它能确保你无法忘记验证某些内容,并防止你无意中将错误数据导入数据库。It also ensures that you can't forget to validate something and inadvertently let bad data into the database.

验证错误 UIValidation Error UI

运行应用并导航到电影控制器。Run the app and navigate to the Movies controller.

点击“新建”连接添加新电影的链接。Tap the Create New link to add a new movie. 使用无效值填写表单。Fill out the form with some invalid values. 当 jQuery 客户端验证检测到错误时,会显示一条错误消息。As soon as jQuery client side validation detects the error, it displays an error message.

带有多个 jQuery 客户端验证错误的电影视图表单

备注

可能无法在小数字段中输入十进制逗号。You may not be able to enter decimal commas in decimal fields. 若要使 jQuery 验证支持使用逗号(“,”)表示小数点的的非英语区域设置,以及支持非美国英语日期格式,必须执行使应用全球化的步骤。To support jQuery validation for non-English locales that use a comma (",") for a decimal point, and non US-English date formats, you must take steps to globalize your app. 有关添加十进制逗号的说明,请参阅 GitHub 问题 4076See this GitHub issue 4076 for instructions on adding decimal comma.

请注意表单如何自动呈现每个包含无效值的字段中相应的验证错误消息。Notice how the form has automatically rendered an appropriate validation error message in each field containing an invalid value. 客户端(使用 JavaScript 和 jQuery)和服务器端(若用户禁用 JavaScript)都必定会遇到这些错误。The errors are enforced both client-side (using JavaScript and jQuery) and server-side (in case a user has JavaScript disabled).

明显的好处在于不需要在 MoviesController 类或 Create.cshtml 视图中更改单个代码行来启用此验证 UI。A significant benefit is that you didn't need to change a single line of code in the MoviesController class or in the Create.cshtml view in order to enable this validation UI. 在本教程前面创建的控制器和视图会自动选取验证规则,这些规则是通过在 Movie 模型类的属性上使用验证特性所指定的。The controller and views you created earlier in this tutorial automatically picked up the validation rules that you specified by using validation attributes on the properties of the Movie model class. 使用 Edit 操作方法测试验证后,即已应用相同的验证。Test validation using the Edit action method, and the same validation is applied.

存在客户端验证错误时,不会将表单数据发送到服务器。The form data isn't sent to the server until there are no client side validation errors. 可通过使用 Fiddler 工具F12 开发人员工具HTTP Post 方法中设置断点来对此进行验证。You can verify this by putting a break point in the HTTP Post method, by using the Fiddler tool , or the F12 Developer tools.

验证工作原理How validation works

你可能想知道在不对控制器或视图中的代码进行任何更新的情况下,验证 UI 是如何生成的。You might wonder how the validation UI was generated without any updates to the code in the controller or views. 下列代码显示两种 Create 方法。The following code shows the two Create methods.

// GET: Movies/Create
public IActionResult Create()
{
    return View();
}

// POST: Movies/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
    [Bind("ID,Title,ReleaseDate,Genre,Price, Rating")] Movie movie)
{
    if (ModelState.IsValid)
    {
        _context.Add(movie);
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    return View(movie);
}

第一个 (HTTP GET) Create 操作方法显示初始的“创建”表单。The first (HTTP GET) Create action method displays the initial Create form. 第二个 ([HttpPost]) 版本处理表单发布。The second ([HttpPost]) version handles the form post. 第二个 Create 方法([HttpPost] 版本)调用 ModelState.IsValid 以检查电影是否有任何验证错误。The second Create method (The [HttpPost] version) calls ModelState.IsValid to check whether the movie has any validation errors. 调用此方法将评估已应用于对象的任何验证特性。Calling this method evaluates any validation attributes that have been applied to the object. 如果对象有验证错误,则 Create 方法会重新显示此表单。If the object has validation errors, the Create method re-displays the form. 如果没有错误,此方法则将新电影保存在数据库中。If there are no errors, the method saves the new movie in the database. 在我们的电影示例中,在检测到客户端上存在验证错误时,表单不会发布到服务器。当存在客户端验证错误时,第二个 Create 方法永远不会被调用。In our movie example, the form isn't posted to the server when there are validation errors detected on the client side; the second Create method is never called when there are client side validation errors. 如果在浏览器中禁用 JavaScript,客户端验证将被禁用,而你可以测试 HTTP POST Create 方法 ModelState.IsValid 检测任何验证错误。If you disable JavaScript in your browser, client validation is disabled and you can test the HTTP POST Create method ModelState.IsValid detecting any validation errors.

可以在 [HttpPost] Create 方法中设置断点,并验证方法从未被调用,客户端验证在检测到存在验证错误时不会提交表单数据。You can set a break point in the [HttpPost] Create method and verify the method is never called, client side validation won't submit the form data when validation errors are detected. 如果在浏览器中禁用 JavaScript,然后提交错误的表单,将触发断点。If you disable JavaScript in your browser, then submit the form with errors, the break point will be hit. 在没有 JavaScript 的情况下仍然可以进行完整的验证。You still get full validation without JavaScript.

以下图片显示如何在 FireFox 浏览器中禁用 JavaScript。The following image shows how to disable JavaScript in the Firefox browser.

Firefox:在“选项”的“内容”选项卡上,取消选中“启用 Javascript”复选框。

以下图片显示如何在 Chrome 浏览器中禁用 JavaScript。The following image shows how to disable JavaScript in the Chrome browser.

Google Chrome:在“内容”设置的 Javascript 部分中,选择“不允许任何网站运行 JavaScript”。

禁用 JavaScript 后,发布无效数据并单步执行调试程序。After you disable JavaScript, post invalid data and step through the debugger.

在对无效数据进行调试时,ModelState.IsValid 上的 Intellisense 显示值为 false。

Create.cshtml 视图模板的一部分在以下标记中显示:The portion of the Create.cshtml view template is shown in the following markup:


<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>           
       
        @*Markup removed for brevity.*@

操作方法使用上述标记来显示初始表单,并在发生错误时重新显示此表单。The preceding markup is used by the action methods to display the initial form and to redisplay it in the event of an error.

输入标记帮助程序使用 DataAnnotations 特性,并在客户端上生成 jQuery 验证所需的 HTML 特性。The Input Tag Helper uses the DataAnnotations attributes and produces HTML attributes needed for jQuery Validation on the client side. 验证标记帮助程序用于显示验证错误。The Validation Tag Helper displays validation errors. 有关详细信息,请参阅验证See Validation for more information.

此方法真正好的一点是:无论是控制器还是 Create 视图模板都不知道强制实施的实际验证规则或显示的特定错误消息。What's really nice about this approach is that neither the controller nor the Create view template knows anything about the actual validation rules being enforced or about the specific error messages displayed. 仅可在 Movie 类中指定验证规则和错误字符串。The validation rules and the error strings are specified only in the Movie class. 这些相同的验证规则自动应用于 Edit 视图和可能创建用于编辑模型的任何其他视图模板。These same validation rules are automatically applied to the Edit view and any other views templates you might create that edit your model.

需要更改验证逻辑时,可以通过将验证特性添加到模型在同一个位置实现此操作。(在此示例中为 Movie 类)。When you need to change validation logic, you can do so in exactly one place by adding validation attributes to the model (in this example, the Movie class). 无需担心对应用程序的不同部分所强制执行规则的方式不一致 - 所有验证逻辑都将定义在一个位置并用于整个应用程序。You won't have to worry about different parts of the application being inconsistent with how the rules are enforced — all validation logic will be defined in one place and used everywhere. 这使代码非常简洁,并且更易于维护和改进。This keeps the code very clean, and makes it easy to maintain and evolve. 这意味着对 DRY 原则的完全遵守。And it means that you'll be fully honoring the DRY principle.

使用 DataType 特性Using DataType Attributes

打开 Movie.cs 文件并检查 Movie 类。Open the Movie.cs file and examine the Movie class. 除了一组内置的验证特性,System.ComponentModel.DataAnnotations 命名空间还提供格式特性。The System.ComponentModel.DataAnnotations namespace provides formatting attributes in addition to the built-in set of validation attributes. 我们已经在发布日期和价格字段中应用了 DataType 枚举值。We've already applied a DataType enumeration value to the release date and to the price fields. 以下代码显示具有适当 DataType 特性的 ReleaseDatePrice 属性。The following code shows the ReleaseDate and Price properties with the appropriate DataType attribute.

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

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

DataType 属性仅提供相关提示来帮助视图引擎设置数据格式(并提供元素/属性,例如向 URL 提供 <a> 和向电子邮件提供 <a href="mailto:EmailAddress.com">)。The DataType attributes only provide hints for the view engine to format the data (and supplies elements/attributes such as <a> for URL's and <a href="mailto:EmailAddress.com"> for email. 可以使用 RegularExpression 特性验证数据的格式。You can use the RegularExpression attribute to validate the format of the data. DataType 属性用于指定比数据库内部类型更具体的数据类型,它们不是验证属性。The DataType attribute is used to specify a data type that's more specific than the database intrinsic type, they're not validation attributes. 在此示例中,我们只想跟踪日期,而不是时间。In this case we only want to keep track of the date, not the time. DataType 枚举提供了多种数据类型,例如日期、时间、电话号码、货币、电子邮件地址等。The DataType Enumeration provides for many data types, such as Date, Time, PhoneNumber, Currency, EmailAddress and more. 应用程序还可通过 DataType 特性自动提供类型特定的功能。The DataType attribute can also enable the application to automatically provide type-specific features. 例如,可以为 DataType.EmailAddress 创建 mailto: 链接,并且可以在支持 HTML5 的浏览器中为 DataType.Date 提供日期选择器。For example, a mailto: link can be created for DataType.EmailAddress, and a date selector can be provided for DataType.Date in browsers that support HTML5. DataType 特性发出 HTML 5 data-(读作 data dash)特性供 HTML 5 浏览器理解。The DataType attributes emit HTML 5 data- (pronounced data dash) attributes that HTML 5 browsers can understand. DataType 特性不提供任何验证。The DataType attributes do not provide any validation.

DataType.Date 不指定显示日期的格式。DataType.Date doesn't specify the format of the date that's displayed. 默认情况下,数据字段根据基于服务器的 CultureInfo 的默认格式进行显示。By default, the data field is displayed according to the default formats based on the server's CultureInfo.

DisplayFormat 特性用于显式指定日期格式:The DisplayFormat attribute is used to explicitly specify the date format:

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

ApplyFormatInEditMode 设置指定在文本框中显示值以进行编辑时也应用格式。The ApplyFormatInEditMode setting specifies that the formatting should also be applied when the value is displayed in a text box for editing. (你可能不想为某些字段执行此操作 — 例如对于货币值,你可能不希望文本框中的货币符号可编辑。)(You might not want that for some fields — for example, for currency values, you probably don't want the currency symbol in the text box for editing.)

可以单独使用 DisplayFormat 特性,但通常建议使用 DataType 特性。You can use the DisplayFormat attribute by itself, but it's generally a good idea to use the DataType attribute. DataType 特性传达数据的语义而不是传达如何在屏幕上呈现数据,并提供 DisplayFormat 不具备的以下优势:The DataType attribute conveys the semantics of the data as opposed to how to render it on a screen, and provides the following benefits that you don't get with DisplayFormat:

  • 浏览器可启用 HTML5 功能(例如显示日历控件、区域设置适用的货币符号、电子邮件链接等)The browser can enable HTML5 features (for example to show a calendar control, the locale-appropriate currency symbol, email links, etc.)

  • 默认情况下,浏览器将根据区域设置采用正确的格式呈现数据。By default, the browser will render data using the correct format based on your locale.

  • DataType 特性使 MVC 能够选择正确的字段模板来呈现数据(如果 DisplayFormat 由自身使用,则使用的是字符串模板)。The DataType attribute can enable MVC to choose the right field template to render the data (the DisplayFormat if used by itself uses the string template).

备注

jQuery 验证不适用于 Range 属性和 DateTimejQuery validation doesn't work with the Range attribute and DateTime. 例如,以下代码将始终显示客户端验证错误,即便日期在指定的范围内:For example, the following code will always display a client side validation error, even when the date is in the specified range:

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

需要禁用 jQuery 日期验证才能使用具有 DateTimeRange 特性。You will need to disable jQuery date validation to use the Range attribute with DateTime. 通常,在模型中编译固定日期是不恰当的,因此不推荐使用 Range 特性和 DateTimeIt's generally not a good practice to compile hard dates in your models, so using the Range attribute and DateTime is discouraged.

以下代码显示组合在一行上的特性:The following code shows combining attributes on one line:

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

    [StringLength(60, MinimumLength = 3)]
    public string Title { get; set; }

    [Display(Name = "Release Date"), DataType(DataType.Date)]
    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)]
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }

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

在本系列的下一部分中,我们将回顾应用,并对自动生成的 DetailsDelete 方法进行一些改进。In the next part of the series, we review the app and make some improvements to the automatically generated Details and Delete methods.

其他资源Additional resources