表单与验证

Blazor的EditForm组件不仅负责生成HTML<form>,还会把表单模型、字段修改状态和验证结果组织到一个EditContext中。它既可用于交互式组件,也可用于.NET 8的静态SSR表单。这篇笔记以学生录入表单为例介绍两种用法。

EditForm和输入组件

下面的交互式表单使用InputText和InputNumber绑定模型属性,并在验证通过后调用保存逻辑。

Components/Pages/StudentCreate.razor

@page "/students/create"
@rendermode InteractiveServer
@inject StudentService StudentService

<EditForm Model="Model" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div>
        <label for="name">姓名</label>
        <InputText id="name" @bind-Value="Model.Name" />
        <ValidationMessage For="() => Model.Name" />
    </div>
    <div>
        <label for="age">年龄</label>
        <InputNumber id="age" @bind-Value="Model.Age" />
        <ValidationMessage For="() => Model.Age" />
    </div>

    <button type="submit">保存</button>
</EditForm>

@code {
    private StudentInput Model { get; set; } = new();

    private async Task HandleValidSubmit()
    {
        await StudentService.CreateAsync(Model);
    }
}

Model属性指定表单编辑的模型对象。Blazor提供了一组与EditForm集成的输入组件,常用的包括:

  • InputText、InputTextArea:文本输入。
  • InputNumber<TValue>:数值输入。
  • InputCheckbox:布尔值输入。
  • InputDate<TValue>:日期输入。
  • InputSelect<TValue>、InputRadioGroup<TValue>:选择和单选输入。

这些组件使用@bind-Value绑定模型属性,而不是普通HTML元素常用的@bind。输入组件会在值发生变化时通知EditContext,使表单能够跟踪哪些字段被修改、哪些字段存在验证错误。静态SSR没有实时交互事件,因此页面上的绑定主要用于输出初始值和生成提交字段,实际的新值会在表单POST后由模型绑定填入。

EditForm还支持OnSubmit和OnInvalidSubmit。OnValidSubmit只在验证成功时执行,OnInvalidSubmit只在验证失败时执行;OnSubmit则不区分验证结果,适合需要自行控制验证流程的场景,这几个提交回调不应混用为同一套重复保存逻辑。

DataAnnotations验证

模型可以使用System.ComponentModel.DataAnnotations中的验证特性声明约束。

using System.ComponentModel.DataAnnotations;

public class StudentInput
{
    [Required(ErrorMessage = "请输入姓名")]
    [StringLength(20, ErrorMessage = "姓名不能超过20个字符")]
    public string Name { get; set; } = string.Empty;

    [Range(1, 150, ErrorMessage = "年龄必须在1到150之间")]
    public int Age { get; set; }
}

只有在EditForm中加入<DataAnnotationsValidator />后,这些特性才会参与Blazor表单验证。<ValidationSummary />集中显示整个表单的错误,<ValidationMessage For="..." />显示指定字段的错误,两者可以按需选择或组合使用。客户端或交互式UI中的验证只能改善体验,服务器保存数据前仍应执行必要的业务验证和授权检查。

直接使用EditContext

EditContext是表单状态和验证状态的核心对象。除了让EditForm根据Model自动创建它,我们也可以自己创建并传入:

<EditForm EditContext="editContext" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <InputText @bind-Value="Model.Name" />
    <button type="submit">保存</button>
</EditForm>

@code {
    private StudentInput Model { get; set; } = new();
    private EditContext editContext = default!;

    protected override void OnInitialized()
    {
        editContext = new EditContext(Model);
    }

    private void HandleValidSubmit()
    {
        // 保存Model。
    }
}

需要动态切换编辑对象、订阅OnFieldChanged或OnValidationStateChanged、手动调用Validate()时,通常会直接操作EditContext。注意EditForm的Model和EditContext参数不能同时设置,否则会在运行时抛出异常。如果订阅了EditContext事件,组件释放时还应取消订阅,避免保留不必要的引用。

静态SSR表单

.NET 8为静态SSR表单引入了FormName和[SupplyParameterFromForm]。静态SSR中没有交互事件,浏览器提交的是普通HTTP表单POST,服务器收到请求后再绑定模型、执行验证和提交回调。

@page "/students/create-static"
@inject StudentService StudentService

<EditForm Model="Model"
          FormName="CreateStudent"
          Enhance
          OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <label for="name">姓名</label>
    <InputText id="name" @bind-Value="Model!.Name" />
    <ValidationMessage For="() => Model!.Name" />

    <button type="submit">保存</button>
</EditForm>

@code {
    [SupplyParameterFromForm]
    private StudentInput? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new StudentInput();
    }

    private async Task HandleValidSubmit()
    {
        await StudentService.CreateAsync(Model!);
    }
}

这里有几点需要注意:

  1. FormName为表单提供明确且唯一的名称,静态SSR中应为每个表单设置它;一个页面放置多个表单时尤其不能重名。
  2. [SupplyParameterFromForm]指示Blazor从提交的表单值创建或填充Model。OnInitialized()中的Model ??= new()同时兼顾首次GET显示空表单和POST绑定已有值。
  3. Enhance启用增强表单提交,在脚本可用时减少整页刷新;没有启用增强导航时,表单仍可通过普通HTTP POST工作。它不会把静态SSR组件变成交互式组件。
  4. 静态SSR表单依赖防伪令牌防止跨站请求伪造。EditForm会处理表单令牌,但应用管道必须调用app.UseAntiforgery(),并确保该中间件在映射Razor组件端点前生效;如果应用使用身份验证和授权中间件,通常将UseAntiforgery()放在它们之后。

Program.cs中的关键顺序如下:

app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

如果未使用身份验证,可以省略前两行,但不能因为表单可以提交就忽略防伪保护。静态SSR表单的每次提交都是新的服务器处理过程,不要依赖上一次请求中普通组件字段仍然保留。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。