ASP.NET Core Blazor 窗体概述

注意

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

警告

不再支持此版本的 ASP.NET Core。 有关详细信息,请参阅 .NET 和 .NET 核心支持策略。 有关当前发布版本,请参阅此文章的 .NET 10 版本

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

输入组件和表单

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

在 Blazor Web App使用静态服务器端呈现(静态 SSR)的情况下,输入组件在表单包含 DataAnnotationsValidator 组件时自动参与客户端验证。 有关详细信息,请参阅 ASP.NET Core 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 块中创建,并保存在属性 publicModel) 中。 [SupplyParameterFromForm]属性指示应从表单数据中提供关联属性的值。 与属性名称匹配的请求中的数据绑定到该属性。
  • InputText 组件是用于编辑字符串值的输入组件。 @bind-Value 指令属性将 Model.Id 模型属性绑定到 InputText 组件的 Value 属性。
  • Submit 方法被注册为 @onsubmit 回调的处理程序。 当用户提交表单时,该处理程序将被调用。

重要

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

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

纯 HTML 表单支持流式呈现。 请注意,在 POST表单时,只有表单处理程序内部的 DOM 更新才会被流式传输(例如,@onsubmit)。 OnInitializedAsync 内的更新仅针对 GET 请求进行流式传输。 有关详细信息,请参阅“允许流式传输 POST 响应的加载阶段”(dotnet/aspnetcore#50994)。

注意

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

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

若要根据其他元素的 DOM 事件(例如 oninputonblur)提交表单,请使用 JavaScript 提交表单(submit)。

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

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

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 块中创建,并保存在属性 publicModel) 中。 系统会将该属性分配给 EditForm.Model 参数。 [SupplyParameterFromForm]属性指示应从表单数据中提供关联属性的值。 与属性名称匹配的请求中的数据绑定到该属性。
  • InputText 组件是用于编辑字符串值的 输入组件@bind-Value 指令属性将 Model.Id 模型属性绑定到 InputText 组件的 Value 属性。
  • Submit 方法注册为 OnSubmit 回调的处理程序。 当用户提交表单时,该处理程序将被调用。

重要

始终使用具有唯一窗体名称的 FormName 属性。

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

支持EditForm。 请注意,在 POST表单时,只有表单处理程序内部的 DOM 更新才会被流式传输(例如,OnValidSubmit)。 OnInitializedAsync 内的更新仅针对 GET 请求进行流式传输。 有关详细信息,请参阅“允许流式传输 POST 响应的加载阶段”(dotnet/aspnetcore#50994)。

注意

指向.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 组件†)使用数据注释附加验证支持:
    • 如果在选择 <input> 按钮时 Submit 表单域为空,则验证摘要(ValidationSummary 组件‡)(“The Id field is required.”)会显示错误,并且系统不会调用 Submit
    • 如果在选择 <input> 按钮时 Submit 窗体字段包含超过十个字符,则验证摘要中显示错误 ("Id is too long.")。 Submit 调用。
    • 如果在选择 <input> 按钮时 Submit 表单域包含有效的值,则系统会调用 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,因为调用事件处理程序后,StateHasChanged 框架会自动调用 Blazor 来重新呈现组件。 如果不使用事件处理程序调用清除窗体或字段的方法,则应使用开发人员代码调用 StateHasChanged 来重新呈现组件。

防伪支持

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

默认情况下,使用 WebApplication.CreateBuilder 构建的应用会启用自动跨站点请求伪造(CSRF)保护中间件。 中间件会对不安全的 HTTP 方法检查 Sec-Fetch-SiteOrigin 标头,并在请求上记录验证结果。 Blazor 服务器端渲染 (SSR) 表单提交会强制执行该判定,并对不受信任的跨源表单提交返回 400 Bad Request

AddRazorComponents 文件中调用 Program 时,基于令牌的防伪造服务会被添加到应用中。 但是,只有通过调用 UseAntiforgery 将防伪造中间件显式添加到请求处理管道中时,才会运行令牌验证。

添加基于令牌的防伪中间件不会替换基于标头的自动 CSRF 保护中间件。 当应用调用 UseAntiforgery时,这两种防御机制都针对表单帖子运行:

  • 基于请求头的 CSRF 保护中间件会先运行,并记录其判定结果。
  • 反伪造中间件执行基于令牌的验证。

防伪造中间件生成的基于令牌的结果具有权威性,并将覆盖先前基于标头的 CSRF 中间件的判定。

若要显式添加防伪中间件,请在调用 UseAntiforgery 后调用 UseRouting。 如果有对 UseRoutingUseEndpoints 的调用,则对 UseAntiforgery 的调用必须介于两者之间。 对 UseAntiforgery 的调用必须在对 UseAuthenticationUseAuthorization 的调用后发出。

若要禁用基于标头的自动 CSRF 保护中间件,请将 DisableCsrfProtection 配置密钥设置为 true。 例如,使用应用设置文件 (appsettings.json) 禁用中间件:

{
  "DisableCsrfProtection": true
}

DisableCsrfProtection配置设置可由任何配置源提供,包括通过环境变量(DisableCsrfProtection=true)。

警告

禁用自动 CSRF 保护中间件会删除整个应用的默认基于标头的 (Sec-Fetch-Site/Origin) 保护。 仅当您提供了替代性的 CSRF 防护措施时,才禁用它,例如通过调用 UseAntiforgery 显式启用基于令牌的反伪造中间件。

有关详细信息,请参阅 自动 CSRF 保护

重要

以下有关 AntiforgeryToken 组件和服务 AntiforgeryStateProvider 的指导仅适用于在请求处理管道中调用 UseAntiforgery 来显式采用基于令牌的防伪中间件的应用。

该应用通过在 UseAntiforgery 文件中的请求处理管道里调用 Program 来使用防伪中间件。 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 的属性来修改数据库。 恶意用户包含的任何额外数据将被丢弃,因此恶意用户无法执行过度发布攻击。

增强型表单处理

通过为Enhance表单提供参数EditForm或为 HTML 表单添加属性data-enhance来增强表单 POST 请求的导航功能(<form>):

<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 startup

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

示例

示例不采用增强的表单处理来进行表单 POST 请求,但可以通过遵循增强型表单处理部分中的指导,更新所有示例以采用这些增强功能。

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

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

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

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

组件使用可以为 null 的引用类型(NRT),.NET编译器执行 null 状态静态分析,这两种分析在 .NET 6 或更高版本中都受支持。 有关详细信息,请参阅 从 .NET 5 的 ASP.NET Core 迁移到 .NET 6

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

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

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

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

@using System.ComponentModel.DataAnnotations

窗体示例引用 Star Trek(星际迷航)世界的各个方面。 星际迷航是 哥伦比亚广播公司工作室派拉蒙的 1966-2023 年版权©。

客户端验证需要电路

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

静态 SSR 表单中的客户端验证

在 Blazor Web Apps 中,采用静态服务器端渲染(静态 SSR)的组件内的表单,在表单中存在 DataAnnotationsValidator 组件时,会自动获得客户端验证。 有关详细信息,请参阅 ASP.NET Core Blazor 表单验证

不支持的验证功能

外,Blazor 支持所有[Remote]

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

  • 针对以下任一方案,请遵循 ASP.NET Core Blazor 表单验证中的指南:
    • 采用交互式呈现模式的 Blazor Web App 中的服务器端验证。
    • 静态 SSR 表单中的客户端验证。
    • 独立 Blazor WebAssembly 应用中的客户端验证。
  • 使用本机 HTML 验证属性(请参阅 客户端表单验证)。
  • 采用第三方验证 JavaScript 库。
  • 请根据 ASP.NET Core Blazor 表单验证中的指南执行以下操作:
    • 采用交互式呈现模式的 Blazor Web App 中的服务器端验证。
    • 独立 Blazor WebAssembly 应用中的客户端验证。
  • 使用本机 HTML 验证属性(请参阅 客户端表单验证)。
  • 采用第三方验证 JavaScript 库。

其他资源