ASP.NET Core Blazor表单概述

注意

此版本不是本文的最新版本。 有关当前版本,请参阅本文.NET 9 版本。

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

有关当前版本,请参阅本文.NET 9 版本。

本文介绍如何使用 Blazor 中的窗体。

输入组件和窗体

Blazor 框架支持窗体并提供内置输入组件:

注意

不支持的验证功能部分介绍了不支持的 ASP.NET Core 验证功能。

Microsoft.AspNetCore.Components.Forms 命名空间提供以下内容:

  • 用于管理窗体元素、状态和验证的类。
  • 访问内置 Input* 组件。

从 Blazor 项目模板创建的项目在应用的 _Imports.razor 文件中包含命名空间,这使命名空间对应用的 Razor 组件可用。

支持标准 HTML 窗体。 使用常规 HTML <form> 标记创建窗体,并指定用于处理提交的窗体请求的 @onsubmit 处理程序。

StarshipPlainForm.razor:

@page "/starship-plain-form"
@inject ILogger<StarshipPlainForm> Logger

<form method="post" @onsubmit="Submit" @formname="starship-plain-form">
    <AntiforgeryToken />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</form>

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

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);

    public class Starship
    {
        public string? Id { get; set; }
    }
}
@page "/starship-plain-form"
@inject ILogger<StarshipPlainForm> Logger

<form method="post" @onsubmit="Submit" @formname="starship-plain-form">
    <AntiforgeryToken />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</form>

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

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);

    public class Starship
    {
        public string? Id { get; set; }
    }
}

在上述的 StarshipPlainForm 组件中:

  • 表单呈现在<form>元素的显示位置。 该窗体使用 @formname 指令属性命名,这会将该窗体唯一标识到 Blazor 框架。
  • 模型在组件的 @code 块中创建,并保存在公共属性 (Model) 中。 [SupplyParameterFromForm]属性指示应从表单数据中提供关联属性的值。 与属性名称匹配的请求中的数据绑定到该属性。
  • InputText 组件是用于编辑字符串值的输入组件。 @bind-Value 指令属性将 Model.Id 模型属性绑定到 InputText 组件的 Value 属性。
  • Submit 方法注册为 @onsubmit 回调的处理程序。 当用户提交窗体时,该处理程序将被调用。

重要

始终使用具有唯一窗体名称的 @formname 指令属性。

Blazor 通过截获请求将响应应用到现有 DOM,从而尽可能保留呈现的窗体,以增强页面导航和窗体处理。 增强功能可避免完全加载页面,可提供更流畅的用户体验,类似于单页应用 (SPA),尽管组件是在服务器上呈现。 有关详细信息,请参阅 ASP.NET Core Blazor 路由和导航

纯 HTML 表单支持流呈现

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

前面的示例在表单中包含AntiforgeryToken组件,以包含防伪支持。 本文的防伪支持部分中进一步介绍了防伪支持。

若要根据另一元素的 DOM 事件(例如 oninputonblur)提交表单,使用 JavaScript 提交表单(submit [MDN 文档])。

使用框架的EditForm组件,通常使用Blazor的内置表单支持以定义表单,而不是在Blazor应用中使用纯表单。 以下 Razor 组件演示了使用 EditForm 组件来呈现 WebForm 的典型元素、组件和 Razor 代码。

窗体是使用 Blazor 框架的 EditForm 组件进行定义的。 以下 Razor 组件演示了使用 EditForm 组件来呈现 WebForm 的典型元素、组件和 Razor 代码。

Starship1.razor:

@page "/starship-1"
@inject ILogger<Starship1> Logger

<EditForm Model="Model" OnSubmit="Submit" FormName="Starship1">
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

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

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);

    public class Starship
    {
        public string? Id { get; set; }
    }
}
@page "/starship-1"
@inject ILogger<Starship1> Logger

<EditForm Model="Model" OnSubmit="Submit" FormName="Starship1">
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

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

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);

    public class Starship
    {
        public string? Id { get; set; }
    }
}

在上述的 Starship1 组件中:

  • EditForm 组件会呈现在显示 <EditForm> 元素的位置。 该窗体使用 @formname 指令属性命名,这会将该窗体唯一标识到 Blazor 框架。
  • 模型在组件的 @code 块中创建,并保存在公共属性 (Model) 中。 系统会将该属性分配给 EditForm.Model 参数。 [SupplyParameterFromForm]属性指示应从表单数据中提供关联属性的值。 与属性名称匹配的请求中的数据绑定到该属性。
  • InputText组件是用于编辑字符串值的输入组件@bind-Value 指令属性将 Model.Id 模型属性绑定到 InputText 组件的 Value 属性。
  • Submit 方法注册为 OnSubmit 回调的处理程序。 当用户提交窗体时,该处理程序将被调用。

重要

始终使用具有唯一窗体名称的 @formname 指令属性。

Blazor增强了EditForm组件的页面导航和表单处理。 有关详细信息,请参阅 ASP.NET Core Blazor 路由和导航

EditForm支持流呈现

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

@page "/starship-1"
@inject ILogger<Starship1> Logger

<EditForm Model="Model" OnSubmit="Submit">
    <InputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Model.Id = {Id}", Model?.Id);
    }

    public class Starship
    {
        public string? Id { get; set; }
    }
}

在上述的 Starship1 组件中:

  • EditForm 组件会呈现在显示 <EditForm> 元素的位置。
  • 该模型在组件的 @code 块中创建,并保存在私有字段 (model) 中。 该字段分配给 EditForm.Model 参数。
  • InputText 组件是用于编辑字符串值的输入组件。 @bind-Value 指令属性将 Model.Id 模型属性绑定到 InputText 组件的 Value 属性†。
  • Submit 方法注册为 OnSubmit 回调的处理程序。 当用户提交窗体时,该处理程序将被调用。

†有关属性绑定的详细信息,请参阅 ASP.NET Core Blazor 数据绑定

在下一个示例中,修改上述组件以在 Starship2 组件中创建窗体:

  • OnSubmit 已替换为 OnValidSubmit,如果窗体在用户提交时有效,则该组件处理分配的事件处理程序。
  • 添加了一个 ValidationSummary 组件,如果在提交窗体时窗体无效,则会显示验证消息。
  • 数据注释验证程序(DataAnnotationsValidator 组件†)使用数据注释附加验证支持:
    • 如果在选择 Submit 按钮时 <input> 表单域为空,则验证摘要(ValidationSummary 组件‡)(“The Id field is required.”)会显示错误,并且系统不会调用 Submit
    • 如果在选择 Submit 按钮时 <input> 窗体字段包含超过十个字符,则验证摘要中显示错误 ("Id is too long.")。 未调用 Submit
    • 如果在选择 Submit 按钮时 <input> 表单域包含有效的值,则系统会调用 Submit

验证程序组件介绍了 DataAnnotationsValidator 组件。 ‡验证摘要和验证消息组件一文介绍了 ValidationSummary 组件。

Starship2.razor:

@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship2">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <label>
        Identifier: 
        <InputText @bind-Value="Model!.Id" />
    </label>
    <button type="submit">Submit</button>
</EditForm>

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

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship2">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <label>
        Identifier: 
        <InputText @bind-Value="Model!.Id" />
    </label>
    <button type="submit">Submit</button>
</EditForm>

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

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <InputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Id = {Id}", Model?.Id);
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

处理窗体提交

EditForm 提供以下回调来处理窗体提交:

  • 使用 OnValidSubmit 分配事件处理程序,让其在提交含有效字段的窗体时运行。
  • 使用 OnInvalidSubmit 分配事件处理程序,让其在提交含无效字段的窗体时运行。
  • 使用 OnSubmit 分配事件处理程序,让其在不考虑窗体字段验证状态的情况下运行。 通过调用事件处理程序方法中的 EditContext.Validate 来验证窗体。 如果 Validate 返回 true,则窗体有效。

清除窗体或字段

将窗体模型清除回默认状态以重置窗体,此操作可以在 EditForm 的标记内部或外部执行:

<button @onclick="ClearForm">Clear form</button>

...

private void ClearForm() => Model = new();

或者,使用显式 Razor 表达式:

<button @onclick="@(() => Model = new())">Clear form</button>

通过将字段的模型值清除回其默认状态来重置字段:

<button @onclick="ResetId">Reset Identifier</button>

...

private void ResetId() => Model!.Id = string.Empty;

或者,使用显式 Razor 表达式:

<button @onclick="@(() => Model!.Id = string.Empty)">Reset Identifier</button>

在前面的示例中不需要调用 StateHasChanged,因为调用事件处理程序后,Blazor 框架会自动调用 StateHasChanged 来重新呈现组件。 如果不使用事件处理程序调用清除窗体或字段的方法,则应使用开发人员代码调用 StateHasChanged 来重新呈现组件。

防伪支持

Program 文件中调用 AddRazorComponents 时,会自动将防伪服务添加到 Blazor 应用。

应用通过在 Program 文件中的处理管道请求中调用 UseAntiforgery 来使用防伪中间件。 UseAntiforgery 在调用 UseRouting 之后调用。 如果有对 UseRoutingUseEndpoints 的调用,则对 UseAntiforgery 的调用必须介于两者之间。 对 UseAntiforgery 的调用必须在对 UseAuthenticationUseAuthorization 的调用后发出。

AntiforgeryToken 组件将防伪标记呈现为隐藏字段,[RequireAntiforgeryToken] 属性启用防伪保护。 如果防伪检查失败,则会引发 400 - Bad Request 响应,并且不会处理窗体。

对于基于 EditForm 的窗体,会自动添加 AntiforgeryToken 组件和 [RequireAntiforgeryToken] 特性以提供防伪保护。

对于基于 HTML <form>元素的表单,请手动将AntiforgeryToken组件添加到表单:

<form method="post" @onsubmit="Submit" @formname="starshipForm">
    <AntiforgeryToken />
    <input id="send" type="submit" value="Send" />
</form>

@if (submitted)
{
    <p>Form submitted!</p>
}

@code{
    private bool submitted = false;

    private void Submit() => submitted = true;
}

警告

对于基于 EditForm 或 HTML <form> 元素的窗体,可以通过将 required: false 传递给 [RequireAntiforgeryToken] 属性来禁用防伪保护。 以下示例禁用防伪,不建议针对公共应用禁用防伪

@using Microsoft.AspNetCore.Antiforgery
@attribute [RequireAntiforgeryToken(required: false)]

有关详细信息,请参阅 ASP.NET Core Blazor 身份验证和授权

缓解过度发布攻击

静态呈现的服务器端表单(例如通常用于使用表单模型在数据库中创建和编辑记录的组件中的表单)可能容易受到过度发布攻击(也称为批量分配攻击)。 当恶意用户向服务器发出 HTML 表单 POST 请求,而服务器处理了不属于呈现表单的属性的数据且开发人员不希望允许用户修改这些数据时,就会发生过度发布攻击。 术语“过度发布”的字面意思是恶意用户使用表单进行了过度发布。

当模型不包含用于创建和更新操作的受限属性时,无需担心过度发布。 但是,在使用基于 SSR 的静态 Blazor 表单时,请务必警惕过度发布。

若要缓解过度发布,建议将针对表单和数据库的单独视图模型/数据传输对象 (DTO) 用于创建(插入)和更新操作。 提交表单时,组件和 C# 代码仅使用视图模型/DTO 的属性来修改数据库。 恶意用户包含的任何额外数据将被丢弃,因此恶意用户无法执行过度发布攻击。

增强型表单处理

EditForm表单的Enhance参数或 HTML 表单 (<form>) 的data-enhance特性对表单 POST 请求采用增强型导航

<EditForm ... Enhance ...>
    ...
</EditForm>
<form ... data-enhance ...>
    ...
</form>

不支持: 无法对上级元素设置增强型导航以启用增强型表单处理。

<div ... data-enhance ...>
    <form ...>
        <!-- NOT enhanced -->
    </form>
</div>

增强型表单发布仅适用于 Blazor 终结点。 将增强型表单发布到非 Blazor 终结点会导致错误。

禁用增强型表单处理:

  • 对于 EditForm,请从表单元素中移除 Enhance 参数(或将其设置为 falseEnhance="false")。
  • 对于 HTML <form>,请从表单元素中移除 data-enhance 特性(或将其设置为 falsedata-enhance="false")。

如果更新后的内容不是服务器呈现的一部分,Blazor 的增强型导航和表单处理可能会撤消对 DOM 的动态更改。 若要保留元素的内容,请使用 data-permanent 特性。

在以下示例中,当页面加载时,<div> 元素的内容由脚本动态更新:

<div data-permanent>
    ...
</div>

若要在全局禁用增强型导航和表单处理,请参阅 ASP.NET Core Blazor 启动

有关使用 enhancedload 事件侦听增强型页面更新的指南,请参阅 ASP.NET Core Blazor 路由和导航

示例

示例不对表单 POST 请求采用增强型表单处理,但所有示例都可以按照增强型表单处理一节中的指导进行更新,以采用增强的功能。

示例使用 C# 9.0 和 .NET 5 引入的目标类型 new 运算符。 在以下示例中,new 运算符未显式声明类型:

public ShipDescription ShipDescription { get; set; } = new();

如果使用 C# 8.0 或更早的版本 (ASP.NET Core 3.1),请修改示例代码以声明类型为 new 运算符:

public ShipDescription ShipDescription { get; set; } = new ShipDescription();

组件使用可空引用类型 (NRT),.NET 编译器执行 null 状态静态分析,.NET 6 或更高版本支持两者。 有关详细信息,请参阅从 ASP.NET Core 5.0 迁移到 6.0

如果使用 C# 9.0 或更低版本(.NET 5 或更低版本),请从示例中删除 NRT。 通常,这只涉及从示例代码的类型中删除问号 (?) 和感叹号 (!)。

面向 .NET 6 或更高版本时,.NET SDK 将隐式全局 using 指令应用于项目。 示例使用记录器记录有关窗体处理的信息,但不需要在组件示例中为 Microsoft.Extensions.Logging 命名空间指定 @using 指令。 有关详细信息,请参阅 .NET 项目 SDK:隐式 using 指令

如果使用 C# 9.0 或更低版本(.NET 5 或更低版本),请在示例所需的任何 API 的 @page 指令之后,将 @using 指令添加到组件顶部。 通过 Visual Studio(右键单击对象并选择“速览定义”)或 .NET API 浏览器查找 API 命名空间。

为了演示窗体如何用于数据注释验证,示例组件依赖于 System.ComponentModel.DataAnnotations API。 如果希望避免在使用数据注释的组件中使用额外代码行,请在整个应用的组件中使用导入文件(_Imports.razor)提供命名空间:

@using System.ComponentModel.DataAnnotations

窗体示例引用 Star Trek(星际迷航)世界的各个方面。 Star Trek(星际迷航)是 CBS StudiosParamount 的版权 ©1966-2023。

客户端验证需要线路

在 Blazor Web App 中,客户端验证需要活动 BlazorSignalR 线路。 组件中采用静态服务器端呈现(静态 SSR)的表单无法使用客户端验证。 采用静态 SSR 的表单会在表单提交后在服务器上进行验证。

不支持的验证功能

[Remote] 验证属性外,Blazor 支持所有数据注释内置验证程序

组件不支持 Razor jQuery 验证。 建议使用以下任一方法:

  • 按照以下任一操作 ASP.NET 核心Blazor表单验证中的指南操作:
    • 采用交互式呈现模式的 Blazor Web App 服务器端验证。
    • 独立 Blazor Web 程序集应用中的客户端验证。
  • 使用本机 HTML 验证属性(请参阅客户端表单验证(MDN 文档)。
  • 采用第三方验证 JavaScript 库。

对于服务器上的静态呈现表单,客户端验证的新机制在 2025 年底考虑了 .NET 10。 有关详细信息,请参阅使用无线路dotnet/aspnetcore(#51040)通过Blazor客户端验证创建服务器呈现的表单。

其他资源