ASP.NET Core Blazor 窗体和验证ASP.NET Core Blazor forms and validation

本文内容

作者:Daniel RothLuke Latham

Blazor 使用数据注释支持窗体和验证。

下面的 ExampleModel 类型使用数据注释定义验证逻辑:

  1. using System.ComponentModel.DataAnnotations;
  2. public class ExampleModel
  3. {
  4. [Required]
  5. [StringLength(10, ErrorMessage = "Name is too long.")]
  6. public string Name { get; set; }
  7. }

窗体是使用 EditForm 组件定义的。以下窗体演示了典型的元素、组件和 Razor 代码:

  1. <EditForm Model="@_exampleModel" OnValidSubmit="HandleValidSubmit">
  2. <DataAnnotationsValidator />
  3. <ValidationSummary />
  4. <InputText id="name" @bind-Value="_exampleModel.Name" />
  5. <button type="submit">Submit</button>
  6. </EditForm>
  7. @code {
  8. private ExampleModel _exampleModel = new ExampleModel();
  9. private void HandleValidSubmit()
  10. {
  11. Console.WriteLine("OnValidSubmit");
  12. }
  13. }

在上面的示例中:

  • 该窗体使用 ExampleModel 类型中定义的验证来验证 name 字段中的用户输入。该模型在组件的 @code 块中创建,并保存在私有字段 (_exampleModel) 中。该字段分配给 <EditForm> 元素的 Model 属性。
  • InputText 组件的 @bind-Value 进行以下绑定:
    • 将模型属性 (_exampleModel.Name) 绑定到 InputText 组件的 Value 属性。
    • 将更改事件委托绑定到 InputText 组件的 ValueChanged 属性。
  • DataAnnotationsValidator 组件使用数据注释附加验证支持。
  • ValidationSummary 组件汇总验证消息。
  • 窗体成功提交(通过验证)时触发 HandleValidSubmit

可使用一组内置的输入组件来接收和验证用户输入。当更改输入和提交窗体时,将验证输入。下表显示了可用的输入组件。

输入组件呈现为…
InputText<input>
InputTextArea<textarea>
InputSelect<select>
InputNumber<input type="number">
InputCheckbox<input type="checkbox">
InputDate<input type="date">

所有输入组件(包括 EditForm)都支持任意属性。与某个组件参数不匹配的所有属性都将添加到呈现的 HTML 元素中。

输入组件为编辑时验证以及更改其 CSS 类以反映字段状态提供默认行为。某些组件包含有用的分析逻辑。例如,InputDateInputNumber 通过将无法分析的值注册为验证错误,以恰当的方式来处理它们。可接受 Null 值的类型也支持目标字段的为 Null 性(例如,int?)。

下面的 Starship 类型使用比之前的 ExampleModel 更大的属性和数据注释集来定义验证逻辑:

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. public class Starship
  4. {
  5. [Required]
  6. [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
  7. public string Identifier { get; set; }
  8. public string Description { get; set; }
  9. [Required]
  10. public string Classification { get; set; }
  11. [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
  12. public int MaximumAccommodation { get; set; }
  13. [Required]
  14. [Range(typeof(bool), "true", "true",
  15. ErrorMessage = "This form disallows unapproved ships.")]
  16. public bool IsValidatedDesign { get; set; }
  17. [Required]
  18. public DateTime ProductionDate { get; set; }
  19. }

在上面的示例中,Description 是可选的,因为不存在任何数据注释。

以下窗体使用 Starship 模型中定义的验证来验证用户输入:

  1. @page "/FormsValidation"
  2. <h1>Starfleet Starship Database</h1>
  3. <h2>New Ship Entry Form</h2>
  4. <EditForm Model="@_starship" OnValidSubmit="HandleValidSubmit">
  5. <DataAnnotationsValidator />
  6. <ValidationSummary />
  7. <p>
  8. <label>
  9. Identifier:
  10. <InputText @bind-Value="_starship.Identifier" />
  11. </label>
  12. </p>
  13. <p>
  14. <label>
  15. Description (optional):
  16. <InputTextArea @bind-Value="_starship.Description" />
  17. </label>
  18. </p>
  19. <p>
  20. <label>
  21. Primary Classification:
  22. <InputSelect @bind-Value="_starship.Classification">
  23. <option value="">Select classification ...</option>
  24. <option value="Exploration">Exploration</option>
  25. <option value="Diplomacy">Diplomacy</option>
  26. <option value="Defense">Defense</option>
  27. </InputSelect>
  28. </label>
  29. </p>
  30. <p>
  31. <label>
  32. Maximum Accommodation:
  33. <InputNumber @bind-Value="_starship.MaximumAccommodation" />
  34. </label>
  35. </p>
  36. <p>
  37. <label>
  38. Engineering Approval:
  39. <InputCheckbox @bind-Value="_starship.IsValidatedDesign" />
  40. </label>
  41. </p>
  42. <p>
  43. <label>
  44. Production Date:
  45. <InputDate @bind-Value="_starship.ProductionDate" />
  46. </label>
  47. </p>
  48. <button type="submit">Submit</button>
  49. <p>
  50. <a href="http://www.startrek.com/">Star Trek</a>,
  51. &copy;1966-2019 CBS Studios, Inc. and
  52. <a href="https://www.paramount.com">Paramount Pictures</a>
  53. </p>
  54. </EditForm>
  55. @code {
  56. private Starship _starship = new Starship();
  57. private void HandleValidSubmit()
  58. {
  59. Console.WriteLine("OnValidSubmit");
  60. }
  61. }

EditForm 创建一个 EditContext 作为级联值来跟踪有关编辑过程的元数据,其中包括已修改的字段和当前的验证消息。EditForm 还为有效和无效的提交提供便捷的事件(OnValidSubmitOnInvalidSubmit)。或者,使用 OnSubmit 触发验证并使用自定义验证代码检查字段值。

如下示例中:

  • 选择“提交”按钮时,将运行 HandleSubmit 方法 。
  • 使用窗体的 EditContext 验证窗体。
  • 通过将 EditContext 传递给 ServerValidate 方法来进一步验证窗体,该方法会调用服务器上的 Web API 终结点(未显示)。
  • 通过检查 isValid 获得客户端和服务器端验证的结果,并根据该结果运行其他代码。
  1. <EditForm EditContext="@_editContext" OnSubmit="@HandleSubmit">
  2. ...
  3. <button type="submit">Submit</button>
  4. </EditForm>
  5. @code {
  6. private Starship _starship = new Starship();
  7. private EditContext _editContext;
  8. protected override void OnInitialized()
  9. {
  10. _editContext = new EditContext(_starship);
  11. }
  12. private async Task HandleSubmit()
  13. {
  14. var isValid = _editContext.Validate() &&
  15. await ServerValidate(_editContext);
  16. if (isValid)
  17. {
  18. ...
  19. }
  20. else
  21. {
  22. ...
  23. }
  24. }
  25. private async Task<bool> ServerValidate(EditContext editContext)
  26. {
  27. var serverChecksValid = ...
  28. return serverChecksValid;
  29. }
  30. }

基于输入事件的 InputTextInputText based on the input event

使用 InputText 组件创建一个使用 input 事件而不是 change 事件的自定义组件。

使用以下标记创建一个组件,并像使用 InputText 一样使用该组件:

  1. @inherits InputText
  2. <input
  3. @attributes="AdditionalAttributes"
  4. class="@CssClass"
  5. value="@CurrentValue"
  6. @oninput="EventCallback.Factory.CreateBinder<string>(
  7. this, __value => CurrentValueAsString = __value, CurrentValueAsString)" />

使用单选按钮Work with radio buttons

使用窗体中的单选按钮时,数据绑定的处理方式与其他元素不同,因为单选按钮是作为一个组进行计算的。每个单选按钮的值是固定的,但单选按钮组的值是所选单选按钮的值。以下示例介绍如何:

  • 处理单选按钮组的数据绑定。
  • 使用自定义 InputRadio 组件支持验证。
  1. @using System.Globalization
  2. @typeparam TValue
  3. @inherits InputBase<TValue>
  4. <input @attributes="AdditionalAttributes" type="radio" value="@SelectedValue"
  5. checked="@(SelectedValue.Equals(Value))" @onchange="OnChange" />
  6. @code {
  7. [Parameter]
  8. public TValue SelectedValue { get; set; }
  9. private void OnChange(ChangeEventArgs args)
  10. {
  11. CurrentValueAsString = args.Value.ToString();
  12. }
  13. protected override bool TryParseValueFromString(string value,
  14. out TValue result, out string errorMessage)
  15. {
  16. var success = BindConverter.TryConvertTo<TValue>(
  17. value, CultureInfo.CurrentCulture, out var parsedValue);
  18. if (success)
  19. {
  20. result = parsedValue;
  21. errorMessage = null;
  22. return true;
  23. }
  24. else
  25. {
  26. result = default;
  27. errorMessage = $"{FieldIdentifier.FieldName} field isn't valid.";
  28. return false;
  29. }
  30. }
  31. }

以下 EditForm 使用前面的 InputRadio 组件来获取和验证用户的评级:

  1. @page "/RadioButtonExample"
  2. @using System.ComponentModel.DataAnnotations
  3. <h1>Radio Button Group Test</h1>
  4. <EditForm Model="_model" OnValidSubmit="HandleValidSubmit">
  5. <DataAnnotationsValidator />
  6. <ValidationSummary />
  7. @for (int i = 1; i <= 5; i++)
  8. {
  9. <label>
  10. <InputRadio name="rate" SelectedValue="i" @bind-Value="_model.Rating" />
  11. @i
  12. </label>
  13. }
  14. <button type="submit">Submit</button>
  15. </EditForm>
  16. <p>You chose: @_model.Rating</p>
  17. @code {
  18. private Model _model = new Model();
  19. private void HandleValidSubmit()
  20. {
  21. Console.WriteLine("valid");
  22. }
  23. public class Model
  24. {
  25. [Range(1, 5)]
  26. public int Rating { get; set; }
  27. }
  28. }

验证支持Validation support

DataAnnotationsValidator 组件使用数据注释将验证支持附加到级联的 EditContext使用数据注释启用对验证的支持需要此显式手势。若要使用不同于数据注释的验证系统,请用自定义实现替换 DataAnnotationsValidator可在以下参考源中检查 ASP.NET Core 的实现:DataAnnotationsValidator/AddDataAnnotationsValidation

Blazor 执行两种类型的验证:

  • 当用户从某个字段中跳出时,将执行字段验证。在字段验证期间,DataAnnotationsValidator 组件将报告的所有验证结果与该字段相关联。
  • 当用户提交窗体时,将执行模型验证。在模型验证期间,DataAnnotationsValidator 组件尝试根据验证结果报告的成员名称来确定字段。与单个成员无关的验证结果将与模型而不是字段相关联。

验证摘要和验证消息组件Validation Summary and Validation Message components

ValidationSummary 组件用于汇总所有验证消息,这与验证摘要标记帮助程序类似:

  1. <ValidationSummary />

使用 Model 参数输出特定模型的验证消息:

  1. <ValidationSummary Model="@_starship" />

ValidationMessage 组件用于显示特定字段的验证消息,这与验证消息标记帮助程序类似。使用 For 属性和一个为模型属性命名的 Lambda 表达式来指定要验证的字段:

  1. <ValidationMessage For="@(() => _starship.MaximumAccommodation)" />

ValidationMessageValidationSummary 组件支持任意属性。与某个组件参数不匹配的所有属性都将添加到生成的 <div><ul> 元素中。

自定义验证属性Custom validation attributes

当使用自定义验证属性时,为确保验证结果与字段正确关联,请在创建 ValidationResult 时传递验证上下文的 MemberName

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. private class MyCustomValidator : ValidationAttribute
  4. {
  5. protected override ValidationResult IsValid(object value,
  6. ValidationContext validationContext)
  7. {
  8. ...
  9. return new ValidationResult("Validation message to user.",
  10. new[] { validationContext.MemberName });
  11. }
  12. }

Blazor 数据注释验证包 data annotations validation package

Microsoft.AspNetCore.Components.DataAnnotations.Validation 是一个使用 DataAnnotationsValidator 组件填补验证体验缺口的验证包。该包目前处于试验阶段

[CompareProperty] 属性[CompareProperty] attribute

CompareAttribute 不适用于 DataAnnotationsValidator 组件,因为它不会将验证结果与特定成员关联。这可能会导致字段级验证的行为与提交时整个模型的验证行为不一致。Microsoft.AspNetCore.Components.DataAnnotations.Validation 试验性包引入了一个附加的验证属性 ComparePropertyAttribute,它可以克服这些限制 。在 Blazor 应用中,[CompareProperty] 可直接替代 [Compare] 属性。

嵌套模型、集合类型和复杂类型Nested models, collection types, and complex types

Blazor 支持结合使用数据注释和内置的 DataAnnotationsValidator 来验证窗体输入。但是,DataAnnotationsValidator 仅验证绑定到窗体的模型的顶级属性(不包括集合类型或复杂类型的属性)。

若要验证绑定模型的整个对象图(包括集合类型和复杂类型的属性),请使用试验性 Microsoft.AspNetCore.Components.DataAnnotations.Validation 包提供的 ObjectGraphDataAnnotationsValidator

  1. <EditForm Model="@_model" OnValidSubmit="HandleValidSubmit">
  2. <ObjectGraphDataAnnotationsValidator />
  3. ...
  4. </EditForm>

[ValidateComplexType] 注释模型属性。在以下模型类中,ShipDescription 类包含附加数据注释,用于在将模型绑定到窗体时进行验证:

Starship.cs

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. public class Starship
  4. {
  5. ...
  6. [ValidateComplexType]
  7. public ShipDescription ShipDescription { get; set; }
  8. ...
  9. }

ShipDescription.cs

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. public class ShipDescription
  4. {
  5. [Required]
  6. [StringLength(40, ErrorMessage = "Description too long (40 char).")]
  7. public string ShortDescription { get; set; }
  8. [Required]
  9. [StringLength(240, ErrorMessage = "Description too long (240 char).")]
  10. public string LongDescription { get; set; }
  11. }

基于窗体验证启用提交按钮Enable the submit button based on form validation

若要基于窗体验证启用和禁用提交按钮,请执行以下操作:

  • 使用窗体的 EditContext 在初始化组件时分配模型。
  • 在上下文的 OnFieldChanged 回调中验证窗体,以启用和禁用提交按钮。
  1. <EditForm EditContext="@_editContext">
  2. <DataAnnotationsValidator />
  3. <ValidationSummary />
  4. ...
  5. <button type="submit" disabled="@_formInvalid">Submit</button>
  6. </EditForm>
  7. @code {
  8. private Starship _starship = new Starship();
  9. private bool _formInvalid = true;
  10. private EditContext _editContext;
  11. protected override void OnInitialized()
  12. {
  13. _editContext = new EditContext(_starship);
  14. _editContext.OnFieldChanged += (_, __) =>
  15. {
  16. _formInvalid = !_editContext.Validate();
  17. StateHasChanged();
  18. };
  19. }
  20. }

在上面的示例中,如果满足以下条件,则将 _formInvalid 设置为 false

  • 窗体已预加载有效的默认值。
  • 你希望在加载窗体时启用提交按钮。

上述方法的副作用是在用户与任何一个字段进行交互后,ValidationSummary 组件都会填充无效的字段。可通过以下方式之一解决此情况:

  • 不在窗体上使用 ValidationSummary 组件。
  • 选择提交按钮时,使 ValidationSummary 组件可见(例如,在 HandleValidSubmit 方法中)。
  1. <EditForm EditContext="@_editContext" OnValidSubmit="HandleValidSubmit">
  2. <DataAnnotationsValidator />
  3. <ValidationSummary style="@_displaySummary" />
  4. ...
  5. <button type="submit" disabled="@_formInvalid">Submit</button>
  6. </EditForm>
  7. @code {
  8. private string _displaySummary = "display:none";
  9. ...
  10. private void HandleValidSubmit()
  11. {
  12. _displaySummary = "display:block";
  13. }
  14. }