ASP.NET Core Blazor 输入组件

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

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

对于当前版本,请参阅此文的 .NET 8 版本

本文介绍 Blazor 的内置输入组件。

输入组件

该 Blazor 框架提供了用于接收和验证用户输入的内置输入组件。 下表中的内置输入组件在带有 EditContextEditForm 中受支持。

表中的组件在 Razor 组件标记中的窗体外也受支持。 当更改输入和提交窗体时,将验证输入。

输入组件 呈现为…
InputCheckbox <input type="checkbox">
InputDate<TValue> <input type="date">
InputFile <input type="file">
InputNumber<TValue> <input type="number">
InputRadio<TValue> <input type="radio">
InputRadioGroup<TValue> InputRadio<TValue> 子组
InputSelect<TValue> <select>
InputText <input>
InputTextArea <textarea>

有关 InputFile 组件的详细信息,请参阅 ASP.NET Core Blazor 文件上传

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

注意

InputRadio<TValue>InputRadioGroup<TValue> 组件在 ASP.NET Core 5.0 或更高版本中可用。 有关详细信息,请选择本文的 5.0 或更高版本。

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

输入组件提供了用于验证字段更改时间的默认行为:

  • 对于窗体中带有 EditContext 的输入组件,默认验证行为包括更新字段 CSS 类,以通过基础 HTML 元素的验证样式表示字段处于有效状态还是无效状态。
  • 对于没有 EditContext 的控件,默认验证会反映处于有效状态还是无效状态,但不会为基础 HTML 元素提供验证样式

某些组件包含有用的分析逻辑。 例如,InputDate<TValue>InputNumber<TValue> 通过将无法分析的值注册为验证错误,以恰当的方式来处理无法分析的值。 可接受 NULL 值的类型也支持目标字段的“为 Null 性”,例如,可为空整数的 int?

有关 InputFile 组件的详细信息,请参阅 ASP.NET Core Blazor 文件上传

窗体示例

本文以及其他表单节点文章的多个示例中使用的 Starship 类型定义了一组包含数据注释的多种属性:

  • Id 是必需的,因为它是通过 RequiredAttribute 进行批注。 Id 需要使用 StringLengthAttribute 的值,其长度至少为一个字符,不超过 16 个字符。
  • Description 是可选的,因为它没有通过 RequiredAttribute 进行批注。
  • 需要 Classification
  • MaximumAccommodation 属性默认为零,但对于每个 RangeAttribute,其值必须介于 1 到 100,000。
  • IsValidatedDesign 要求属性具有 true 值,当属性绑定到 UI (<input type="checkbox">) 中的复选框时,该属性与选定状态相匹配。
  • ProductionDateDateTime,并且是必需的。

Starship.cs

using System.ComponentModel.DataAnnotations;

namespace BlazorSample;

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

    public string? Description { get; set; }

    [Required]
    public string? Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", ErrorMessage = "Approval required.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}
using System.ComponentModel.DataAnnotations;

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

    public string? Description { get; set; }

    [Required]
    public string? Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}
using System.ComponentModel.DataAnnotations;

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

    public string? Description { get; set; }

    [Required]
    public string? Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    [Required]
    [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
    public string Id { get; set; }

    public string Description { get; set; }

    [Required]
    public string Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    [Required]
    [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
    public string Id { get; set; }

    public string Description { get; set; }

    [Required]
    public string Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}

以下窗体通过以下内容接受和验证用户输入:

  • 在上述 Starship 模型中定义的属性和验证。
  • Blazor 的多个内置输入组件。

设置船舶分类 (Classification) 的模型属性时,将选中与模型匹配的选项。 例如,checked="@(Model!.Classification == "Exploration")" 表示探险船的分类。 显式设置选中选项的原因是 <select> 元素的值仅存在于浏览器中。 如果表单在提交后呈现于服务器上,则客户端的任何状态都将由服务器的状态替代,该状态通常不会将选项标记为已选中。 通过从模型属性设置选中的选项,分类始终会反映模型的状态。 这会保留每次提交表单时的分类选择,从而导致表单在服务器上重新呈现。 如果未在服务器上重新呈现窗体,例如,当交互式服务器呈现模式直接应用于组件时,不需要显式分配选中的选项,因为 Blazor 会在客户端上保留 <select> 元素的状态。

Starship3.razor

@page "/starship-3"
@inject ILogger<Starship3> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship3">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <label>
            Description (optional): 
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification: 
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">
                    Select classification ...
                </option>
                <option checked="@(Model!.Classification == "Exploration")" 
                    value="Exploration">
                    Exploration
                </option>
                <option checked="@(Model!.Classification == "Diplomacy")" 
                    value="Diplomacy">
                    Diplomacy
                </option>
                <option checked="@(Model!.Classification == "Defense")" 
                    value="Defense">
                    Defense
                </option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation: 
            <InputNumber @bind-Value="Model!.MaximumAccommodation" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval: 
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" />
        </label>
    </div>
    <div>
        <label>
            Production Date: 
            <InputDate @bind-Value="Model!.ProductionDate" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

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

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        Logger.LogInformation("Id = {Id} Description = {Description} " +
            "Classification = {Classification} MaximumAccommodation = " +
            "{MaximumAccommodation} IsValidatedDesign = " +
            "{IsValidatedDesign} ProductionDate = {ProductionDate}",
            Model?.Id, Model?.Description, Model?.Classification,
            Model?.MaximumAccommodation, Model?.IsValidatedDesign,
            Model?.ProductionDate);
    }
}
@page "/starship-3"
@inject ILogger<Starship3> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

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

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        Logger.LogInformation("Id = {Id} Description = {Description} " +
            "Classification = {Classification} MaximumAccommodation = " +
            "{MaximumAccommodation} IsValidatedDesign = " +
            "{IsValidatedDesign} ProductionDate = {ProductionDate}", 
            Model?.Id, Model?.Description, Model?.Classification, 
            Model?.MaximumAccommodation, Model?.IsValidatedDesign, 
            Model?.ProductionDate);
    }
}

上述示例中的 EditForm 基于分配的 Starship 实例创建 EditContext (Model="...") 并处理有效的窗体。 下一个示例演示如何将 EditContext 分配给窗体并在提交窗体时进行验证。

在下面的示例中:

  • 使用前面的 Starfleet Starship Database 窗体的缩写版本(Starship3 组件),它仅接受星际飞船的 ID 的值。当创建 Starship 类型的实例时,其他 Starship 属性将接收有效的默认值。
  • 选择 Submit 按钮时,执行 Submit 方法。
  • 通过调用 Submit 中的 EditContext.Validate 来验证窗体。
  • 根据验证结果执行其他日志记录。

注意

下一个示例中的 Submit 会作为异步方法演示,因为存储窗体值通常使用异步调用 (await ...)。 如果在测试应用程序中使用该窗体(如图所示),则 Submit 只同步运行。 出于测试目的,请忽略以下版本警告:

此异步方法缺少“await”运算符,因此将以同步方式运行。 ...

Starship4.razor

@page "/starship-4"
@inject ILogger<Starship4> Logger

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

@code {
    private EditContext? editContext;

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

    protected override void OnInitialized()
    {
        Model ??=
            new()
                {
                    Id = "NCC-1701",
                    Classification = "Exploration",
                    MaximumAccommodation = 150,
                    IsValidatedDesign = true,
                    ProductionDate = new DateTime(2245, 4, 11)
                };
        editContext = new(Model);
    }

    private async Task Submit()
    {
        if (editContext != null && editContext.Validate())
        {
            Logger.LogInformation("Submit called: Form is valid");

            // await ...
        }
        else
        {
            Logger.LogInformation("Submit called: Form is INVALID");
        }
    }
}
@page "/starship-4"
@inject ILogger<Starship4> Logger

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

@code {
    private EditContext? editContext;

    private Starship Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= 
            new()
            {
                Id = "NCC-1701",
                Classification = "Exploration",
                MaximumAccommodation = 150,
                IsValidatedDesign = true,
                ProductionDate = new DateTime(2245, 4, 11)
            };
        editContext = new(Model);
    }

    private async Task Submit()
    {
        if (editContext != null && editContext.Validate())
        {
            Logger.LogInformation("Submit called: Form is valid");

            // await ...
        }
        else
        {
            Logger.LogInformation("Submit called: Form is INVALID");
        }
    }
}

注意

不支持在分配 EditContext 之后改变它。

使用 InputSelect 组件的多个选项选择

绑定支持使用 InputSelect<TValue> 组件的 multiple 选项选择。 @onchange 事件通过事件参数 (ChangeEventArgs) 提供一个选定选项的数组。 值必须绑定到数组类型,与数组类型的绑定使 multiple 属性在 InputSelect<TValue> 标记上是可选的。

在以下示例中,用户必须至少选择两个 starship 分类,但不能超过三个分类。

Starship5.razor

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

<h1>Bind Multiple <code>InputSelect</code> Example</h1>

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship5">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Select classifications (Minimum: 2, Maximum: 3):
            <InputSelect @bind-Value="Model!.SelectedClassification">
                <option value="@Classification.Exploration">Exploration</option>
                <option value="@Classification.Diplomacy">Diplomacy</option>
                <option value="@Classification.Defense">Defense</option>
                <option value="@Classification.Research">Research</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@if (Model?.SelectedClassification?.Length > 0)
{
    <div>@string.Join(", ", Model.SelectedClassification)</div>
}

@code {
    private EditContext? editContext;

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

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

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    private class Starship
    {
        [Required]
        [MinLength(2, ErrorMessage = "Select at least two classifications.")]
        [MaxLength(3, ErrorMessage = "Select no more than three classifications.")]
        public Classification[]? SelectedClassification { get; set; } =
            new[] { Classification.None };
    }

    private enum Classification { None, Exploration, Diplomacy, Defense, Research }
}
@page "/starship-5"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship5> Logger

<h1>Bind Multiple <code>InputSelect</code> Example</h1>

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Select classifications (Minimum: 2, Maximum: 3):
            <InputSelect @bind-Value="Model!.SelectedClassification">
                <option value="@Classification.Exploration">Exploration</option>
                <option value="@Classification.Diplomacy">Diplomacy</option>
                <option value="@Classification.Defense">Defense</option>
                <option value="@Classification.Research">Research</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@if (Model?.SelectedClassification?.Length > 0)
{
    <div>@string.Join(", ", Model.SelectedClassification)</div>
}

@code {
    private EditContext? editContext;

    private Starship? Model { get; set; }

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

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    private class Starship
    {
        [Required]
        [MinLength(2, ErrorMessage = "Select at least two classifications.")]
        [MaxLength(3, ErrorMessage = "Select no more than three classifications.")]
        public Classification[]? SelectedClassification { get; set; } =
            new[] { Classification.None };
    }

    private enum Classification { None, Exploration, Diplomacy, Defense, Research }
}

有关如何在数据绑定中处理空字符串和 null 值的信息,请参阅InputSelect 选项绑定到 C# 对象 null部分。

InputSelect 选项绑定到 C# 对象 null

有关如何在数据绑定中处理空字符串和 null 值的信息,请参阅 ASP.NET Core Blazor 数据绑定

显示名称支持

一些内置组件支持带有 InputBase<TValue>.DisplayName 参数的显示名称。

示例窗体部分的 Starfleet Starship Database 窗体(Starship3组件)中,新 starship 的生产日期不会指定显示名称:

<label>
    Production Date:
    <InputDate @bind-Value="Model!.ProductionDate" />
</label>

如果在提交窗体时该字段包含无效日期,则错误消息不会显示易记名称。 如果出现在验证摘要中,字段名称“ProductionDate”的“Production”和“Date”之间不存在空格:

“生产日期”字段必须填写日期。

DisplayName 属性设置为一个易记名称,其中“Production”和“Date”单词之间有空格:

<label>
    Production Date:
    <InputDate @bind-Value="Model!.ProductionDate" 
        DisplayName="Production Date" />
</label>

当字段值无效时,验证摘要将显示易记名称:

“生产日期”字段必须填写日期。

错误消息模板支持

InputDate<TValue>InputNumber<TValue> 支持错误消息模板:

在 分配了易记显示名称窗体示例部分的 Starfleet Starship Database 窗体(Starship3 组件)中,Production Date 字段使用以下默认错误消息模板生成错误消息:

The {0} field must be a date.

占位符的位置 {0} 是在 DisplayName 向用户显示错误时,属性的值出现的位置。

<label>
    Production Date:
    <InputDate @bind-Value="Model!.ProductionDate" 
        DisplayName="Production Date" />
</label>

“生产日期”字段必须填写日期。

将自定义模板分配给 ParsingErrorMessage 以提供自定义消息:

<label>
    Production Date:
    <InputDate @bind-Value="Model!.ProductionDate" 
        DisplayName="Production Date" 
        ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>

“生产日期”字段包含不正确的日期值。

窗体示例部分的 Starfleet Starship Database 窗体(Starship3 组件)中,使用默认错误消息模板:

The {0} field must be a date.

占位符的位置 {0} 是在 DisplayName 向用户显示错误时,属性的值出现的位置。

<label>
    Production Date:
    <InputDate @bind-Value="Model!.ProductionDate" />
</label>

“生产日期”字段必须填写日期。

将自定义模板分配给 ParsingErrorMessage 以提供自定义消息:

<label>
    Production Date:
    <InputDate @bind-Value="Model!.ProductionDate" 
        ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>

“生产日期”字段的日期值不正确。