collect-user-input

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Collect User Input

收集用户输入

Step 1 — Read the Project's AGENTS.md

步骤1 — 阅读项目的AGENTS.md

Check
AGENTS.md
for Interactivity Mode and Interactivity Scope. This determines which form patterns apply:
ModeForm mechanism
None (Static SSR)
EditForm
with
FormName
+
[SupplyParameterFromForm]
. No
@bind
, no
@onchange
.
Server
EditForm
with
@bind-Value
. Full interactivity — real-time validation, dynamic UI.
WebAssemblySame as Server, but validators needing server data must call APIs.
AutoSame as WebAssembly — code must work in both browser and server.
ScopeImpact
GlobalAll forms are interactive.
FormName
only needed when explicitly opting a page to static SSR.
Per-pageForms in static pages use
FormName
+
[SupplyParameterFromForm]
. Forms in
@rendermode
pages use
@bind-Value
.
查看
AGENTS.md
中的交互模式交互范围,这将决定适用的表单模式:
模式表单机制
None (Static SSR)搭配
FormName
+
[SupplyParameterFromForm]
EditForm
。不支持
@bind
@onchange
Server搭配
@bind-Value
EditForm
。完全交互——实时验证、动态UI。
WebAssembly与Server模式相同,但需要服务器数据的验证器必须调用API。
Auto与WebAssembly模式相同——代码需同时兼容浏览器和服务器环境。
范围影响
Global所有表单均为交互式。仅当明确将页面设置为静态SSR时才需要
FormName
Per-page静态页面中的表单使用
FormName
+
[SupplyParameterFromForm]
@rendermode
页面中的表单使用
@bind-Value

EditForm Setup

EditForm 配置

EditForm
requires either
Model
or
EditContext
— never both.
EditForm
需要要么
Model
要么
EditContext
——绝不能同时使用两者。

Model-based (default)

基于Model(默认)

razor
<EditForm Model="Employee" OnValidSubmit="HandleSubmit" FormName="employee">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <label>
        Name: <InputText @bind-Value="Employee!.Name" />
        <ValidationMessage For="() => Employee!.Name" />
    </label>

    <button type="submit">Save</button>
</EditForm>

@code {
    [SupplyParameterFromForm]
    private EmployeeModel? Employee { get; set; }

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

    private async Task HandleSubmit()
    {
        // Save Employee
    }
}
This single pattern works in both SSR and interactive modes:
  • In SSR:
    FormName
    identifies the form,
    [SupplyParameterFromForm]
    binds POST data,
    ??=
    initializes on GET.
  • In interactive:
    @bind-Value
    provides two-way binding,
    [SupplyParameterFromForm]
    is ignored,
    FormName
    is harmless.
razor
<EditForm Model="Employee" OnValidSubmit="HandleSubmit" FormName="employee">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <label>
        姓名: <InputText @bind-Value="Employee!.Name" />
        <ValidationMessage For="() => Employee!.Name" />
    </label>

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

@code {
    [SupplyParameterFromForm]
    private EmployeeModel? Employee { get; set; }

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

    private async Task HandleSubmit()
    {
        // 保存Employee
    }
}
该单一模式可在SSR和交互式模式下同时工作:
  • 在SSR中:
    FormName
    标识表单,
    [SupplyParameterFromForm]
    绑定POST数据,
    ??=
    在GET请求时初始化模型。
  • 在交互式模式中:
    @bind-Value
    提供双向绑定,
    [SupplyParameterFromForm]
    会被忽略,
    FormName
    无负面影响。

EditContext-based (advanced)

基于EditContext(进阶)

Use when you need programmatic field tracking, dynamic validation rules, or manual
EditContext.Validate()
calls:
csharp
private EditContext? editContext;
private EmployeeModel model = new();

protected override void OnInitialized()
{
    editContext = new EditContext(model);
}
razor
<EditForm EditContext="editContext" OnValidSubmit="HandleSubmit" FormName="employee">
当需要程序化字段跟踪、动态验证规则或手动调用
EditContext.Validate()
时使用:
csharp
private EditContext? editContext;
private EmployeeModel model = new();

protected override void OnInitialized()
{
    editContext = new EditContext(model);
}
razor
<EditForm EditContext="editContext" OnValidSubmit="HandleSubmit" FormName="employee">

Submit Handlers

提交处理器

HandlerFires whenUse when
OnValidSubmit
Validation passesStandard forms with
DataAnnotationsValidator
OnInvalidSubmit
Validation failsNeed custom handling for invalid state
OnSubmit
Always — validation is manualUsing
EditContext.Validate()
yourself
OnSubmit
cannot combine with
OnValidSubmit
/
OnInvalidSubmit
.
处理器触发时机使用场景
OnValidSubmit
验证通过时使用
DataAnnotationsValidator
的标准表单
OnInvalidSubmit
验证失败时需要对无效状态进行自定义处理
OnSubmit
始终触发——需手动验证自行使用
EditContext.Validate()
OnSubmit
不能与
OnValidSubmit
/
OnInvalidSubmit
组合使用。

Built-in Input Components

内置输入组件

ComponentBinds toNotes
InputText
string
Renders
<input type="text">
InputTextArea
string
Renders
<textarea>
InputNumber<T>
int
,
double
,
decimal
Renders
<input type="number">
InputDate<T>
DateTime
,
DateOnly
,
DateTimeOffset
Renders
<input type="date">
InputCheckbox
bool
Renders
<input type="checkbox">
InputSelect<T>
string
, enums, numeric types
Renders
<select>
InputRadioGroup<T>
string
, enums, numeric types
Wraps
InputRadio<T>
children
InputFile
IBrowserFile
File upload — interactive modes only
All input components use
@bind-Value
for binding. Always wrap text in a
<label>
or use
id
/
for
attributes for accessibility.
组件绑定类型说明
InputText
string
渲染为
<input type="text">
InputTextArea
string
渲染为
<textarea>
InputNumber<T>
int
,
double
,
decimal
渲染为
<input type="number">
InputDate<T>
DateTime
,
DateOnly
,
DateTimeOffset
渲染为
<input type="date">
InputCheckbox
bool
渲染为
<input type="checkbox">
InputSelect<T>
string
, 枚举, 数值类型
渲染为
<select>
InputRadioGroup<T>
string
, 枚举, 数值类型
包裹
InputRadio<T>
子组件
InputFile
IBrowserFile
文件上传——仅支持交互式模式
所有输入组件均使用
@bind-Value
进行绑定。为了 accessibility,始终将文本包裹在
<label>
中,或使用
id
/
for
属性。

InputSelect with enum values

搭配枚举值的InputSelect

razor
<InputSelect @bind-Value="Model!.Status">
    <option value="">-- Select --</option>
    @foreach (var value in Enum.GetValues<OrderStatus>())
    {
        <option value="@value">@value</option>
    }
</InputSelect>
razor
<InputSelect @bind-Value="Model!.Status">
    <option value="">-- 请选择 --</option>
    @foreach (var value in Enum.GetValues<OrderStatus>())
    {
        <option value="@value">@value</option>
    }
</InputSelect>

InputRadioGroup

InputRadioGroup

razor
<InputRadioGroup @bind-Value="Model!.Priority">
    @foreach (var p in Enum.GetValues<Priority>())
    {
        <label>
            <InputRadio Value="p" /> @p
        </label>
    }
</InputRadioGroup>
razor
<InputRadioGroup @bind-Value="Model!.Priority">
    @foreach (var p in Enum.GetValues<Priority>())
    {
        <label>
            <InputRadio Value="p" /> @p
        </label>
    }
</InputRadioGroup>

Validation

验证

Data annotations

数据注解

Define validation rules on the model:
csharp
public class EmployeeModel
{
    [Required, StringLength(100)]
    public string? Name { get; set; }

    [Required, EmailAddress]
    public string? Email { get; set; }

    [Range(18, 99)]
    public int Age { get; set; }

    [Required]
    public string? Department { get; set; }
}
Add
<DataAnnotationsValidator />
inside
EditForm
— without it, annotation attributes are silently ignored.
Display errors with:
  • <ValidationSummary />
    — all errors in a list
  • <ValidationMessage For="() => Model!.FieldName" />
    — per-field inline errors
在模型上定义验证规则:
csharp
public class EmployeeModel
{
    [Required, StringLength(100)]
    public string? Name { get; set; }

    [Required, EmailAddress]
    public string? Email { get; set; }

    [Range(18, 99)]
    public int Age { get; set; }

    [Required]
    public string? Department { get; set; }
}
EditForm
内添加
<DataAnnotationsValidator />
——如果没有它,注解属性会被静默忽略。
通过以下方式显示错误:
  • <ValidationSummary />
    ——以列表形式显示所有错误
  • <ValidationMessage For="() => Model!.FieldName" />
    ——显示单个字段的内联错误

Custom validator component

自定义验证器组件

For server-round-trip validation (uniqueness checks, business rules):
csharp
public class CustomValidator : ComponentBase
{
    [CascadingParameter]
    private EditContext? EditContext { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        messageStore = new ValidationMessageStore(EditContext!);
        EditContext!.OnValidationRequested += (s, e) => messageStore.Clear();
        EditContext!.OnFieldChanged += (s, e) => messageStore.Clear(e.FieldIdentifier);
    }

    public void DisplayErrors(Dictionary<string, List<string>> errors)
    {
        foreach (var (field, messages) in errors)
        {
            foreach (var message in messages)
            {
                messageStore!.Add(EditContext!.Field(field), message);
            }
        }
        EditContext!.NotifyValidationStateChanged();
    }

    public void ClearErrors()
    {
        messageStore?.Clear();
        EditContext?.NotifyValidationStateChanged();
    }
}
Usage in a form:
razor
<EditForm Model="Model" OnValidSubmit="HandleSubmit" FormName="register">
    <DataAnnotationsValidator />
    <CustomValidator @ref="customValidator" />
    <ValidationSummary />
    @* inputs *@
</EditForm>

@code {
    private CustomValidator? customValidator;

    private async Task HandleSubmit()
    {
        var errors = await RegistrationService.ValidateAsync(Model!);
        if (errors.Count > 0)
        {
            customValidator!.DisplayErrors(errors);
            return;
        }
        // proceed
    }
}
用于服务器往返验证(唯一性检查、业务规则):
csharp
public class CustomValidator : ComponentBase
{
    [CascadingParameter]
    private EditContext? EditContext { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        messageStore = new ValidationMessageStore(EditContext!);
        EditContext!.OnValidationRequested += (s, e) => messageStore.Clear();
        EditContext!.OnFieldChanged += (s, e) => messageStore.Clear(e.FieldIdentifier);
    }

    public void DisplayErrors(Dictionary<string, List<string>> errors)
    {
        foreach (var (field, messages) in errors)
        {
            foreach (var message in messages)
            {
                messageStore!.Add(EditContext!.Field(field), message);
            }
        }
        EditContext!.NotifyValidationStateChanged();
    }

    public void ClearErrors()
    {
        messageStore?.Clear();
        EditContext?.NotifyValidationStateChanged();
    }
}
在表单中使用:
razor
<EditForm Model="Model" OnValidSubmit="HandleSubmit" FormName="register">
    <DataAnnotationsValidator />
    <CustomValidator @ref="customValidator" />
    <ValidationSummary />
    @* 输入控件 *@
</EditForm>

@code {
    private CustomValidator? customValidator;

    private async Task HandleSubmit()
    {
        var errors = await RegistrationService.ValidateAsync(Model!);
        if (errors.Count > 0)
        {
            customValidator!.DisplayErrors(errors);
            return;
        }
        // 继续执行
    }
}

React to Input Changes (Interactive Only)

响应用户输入变化(仅交互式模式)

@bind:after

@bind:after

Run logic after a bound value changes:
razor
<InputText @bind-Value="Model!.ZipCode" @bind:after="OnZipCodeChanged" />

@code {
    private async Task OnZipCodeChanged()
    {
        // Fetch city/state based on new zip code
        var location = await LocationService.LookupAsync(Model!.ZipCode);
        Model.City = location?.City;
        Model.State = location?.State;
    }
}
在绑定值变化后执行逻辑:
razor
<InputText @bind-Value="Model!.ZipCode" @bind:after="OnZipCodeChanged" />

@code {
    private async Task OnZipCodeChanged()
    {
        // 根据新邮政编码获取城市/州信息
        var location = await LocationService.LookupAsync(Model!.ZipCode);
        Model.City = location?.City;
        Model.State = location?.State;
    }
}

@oninput for real-time filtering

@oninput 用于实时筛选

razor
<input type="text" @oninput="OnSearchInput" placeholder="Search..." />

@code {
    private string searchTerm = "";
    private List<Item> filteredItems = new();

    private void OnSearchInput(ChangeEventArgs e)
    {
        searchTerm = e.Value?.ToString() ?? "";
        filteredItems = allItems.Where(i =>
            i.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)).ToList();
    }
}
razor
<input type="text" @oninput="OnSearchInput" placeholder="搜索..." />

@code {
    private string searchTerm = "";
    private List<Item> filteredItems = new();

    private void OnSearchInput(ChangeEventArgs e)
    {
        searchTerm = e.Value?.ToString() ?? "";
        filteredItems = allItems.Where(i =>
            i.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)).ToList();
    }
}

SSR-Specific Patterns

SSR专属模式

These apply when the form renders in Static SSR (mode = None, or per-page without
@rendermode
).
这些适用于表单在静态SSR(模式=None,或未设置
@rendermode
的单页面)中渲染的场景。

SupplyParameterFromForm

SupplyParameterFromForm

Binds POST data to a property on form submission:
csharp
[SupplyParameterFromForm]
private ContactModel? Contact { get; set; }

protected override void OnInitialized() => Contact ??= new();
Critical: The
??=
in
OnInitialized
is required. On GET the property is null —
??=
creates the model. On POST the framework populates it —
??=
preserves the posted values.
在表单提交时将POST数据绑定到属性:
csharp
[SupplyParameterFromForm]
private ContactModel? Contact { get; set; }

protected override void OnInitialized() => Contact ??= new();
关键注意事项:
OnInitialized
中的
??=
是必需的。在GET请求时属性为null——
??=
会创建模型;在POST请求时框架会填充该属性——
??=
会保留提交的值。

FormName — multiple forms on one page

FormName — 单页面多表单

Each form needs a unique
FormName
:
razor
<EditForm Model="Search" OnSubmit="DoSearch" FormName="search">...</EditForm>
<EditForm Model="Contact" OnValidSubmit="SaveContact" FormName="contact">...</EditForm>
Match
[SupplyParameterFromForm]
to its form:
csharp
[SupplyParameterFromForm(FormName = "search")]
private SearchModel? Search { get; set; }

[SupplyParameterFromForm(FormName = "contact")]
private ContactModel? Contact { get; set; }
每个表单需要唯一的
FormName
razor
<EditForm Model="Search" OnSubmit="DoSearch" FormName="search">...</EditForm>
<EditForm Model="Contact" OnValidSubmit="SaveContact" FormName="contact">...</EditForm>
[SupplyParameterFromForm]
与对应表单匹配:
csharp
[SupplyParameterFromForm(FormName = "search")]
private SearchModel? Search { get; set; }

[SupplyParameterFromForm(FormName = "contact")]
private ContactModel? Contact { get; set; }

Enhanced navigation for forms

表单的增强型导航

Add
Enhance
for SPA-like form submissions without full page reload:
razor
<EditForm Model="Model" OnValidSubmit="Save" FormName="quick" Enhance>
Enhanced forms submit via
fetch
, patch the DOM, and preserve scroll position. The page stays interactive-feeling even in SSR.
添加
Enhance
以实现类SPA的表单提交,无需整页刷新:
razor
<EditForm Model="Model" OnValidSubmit="Save" FormName="quick" Enhance>
增强型表单通过
fetch
提交、修补DOM并保留滚动位置。即使在SSR中,页面也能保持交互式体验。

Plain HTML forms

纯HTML表单

When using raw
<form>
instead of
EditForm
in SSR, add the antiforgery token manually:
razor
<form method="post" @onsubmit="Submit" @formname="raw-form">
    <AntiforgeryToken />
    <input name="Model.Name" value="@Model?.Name" />
    <button type="submit">Send</button>
</form>
EditForm
includes the antiforgery token automatically.
在SSR中使用原生
<form>
而非
EditForm
时,需手动添加防伪令牌:
razor
<form method="post" @onsubmit="Submit" @formname="raw-form">
    <AntiforgeryToken />
    <input name="Model.Name" value="@Model?.Name" />
    <button type="submit">提交</button>
</form>
EditForm
会自动包含防伪令牌。

File Upload

文件上传

InputFile
works in interactive modes only — not in Static SSR.
razor
<InputFile OnChange="OnFileSelected" accept=".pdf,.jpg,.png" />

@code {
    private IBrowserFile? selectedFile;

    private async Task OnFileSelected(InputFileChangeEventArgs e)
    {
        selectedFile = e.File;

        // Read stream with size limit
        await using var stream = selectedFile.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
        // Process stream — save to disk, upload to storage, etc.
    }
}
Stream size limits:
  • Server: Default ~30 KB SignalR message size. Call
    OpenReadStream(maxAllowedSize)
    to increase. Large files stream over the circuit.
  • WebAssembly: File is read in the browser. No SignalR limit, but memory constrained.
For multiple files:
razor
<InputFile OnChange="OnFilesSelected" multiple />

@code {
    private async Task OnFilesSelected(InputFileChangeEventArgs e)
    {
        foreach (var file in e.GetMultipleFiles(maxAllowedFiles: 10))
        {
            await using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
            // Process each file
        }
    }
}
InputFile
仅在交互式模式下工作——不支持静态SSR。
razor
<InputFile OnChange="OnFileSelected" accept=".pdf,.jpg,.png" />

@code {
    private IBrowserFile? selectedFile;

    private async Task OnFileSelected(InputFileChangeEventArgs e)
    {
        selectedFile = e.File;

        // 读取流并设置大小限制
        await using var stream = selectedFile.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
        // 处理流——保存到磁盘、上传到存储等
    }
}
流大小限制:
  • Server: 默认约30 KB SignalR消息大小。调用
    OpenReadStream(maxAllowedSize)
    可增大限制。大文件会通过电路流式传输。
  • WebAssembly: 文件在浏览器中读取。无SignalR限制,但受内存约束。
处理多文件:
razor
<InputFile OnChange="OnFilesSelected" multiple />

@code {
    private async Task OnFilesSelected(InputFileChangeEventArgs e)
    {
        foreach (var file in e.GetMultipleFiles(maxAllowedFiles: 10))
        {
            await using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
            // 处理每个文件
        }
    }
}

Prevent Double Submission

防止重复提交

Disable the submit button while processing:
razor
<button type="submit" disabled="@isSubmitting">
    @(isSubmitting ? "Saving..." : "Save")
</button>

@code {
    private bool isSubmitting;

    private async Task HandleSubmit()
    {
        isSubmitting = true;
        try
        {
            await SaveService.SaveAsync(Model!);
        }
        finally
        {
            isSubmitting = false;
        }
    }
}
在处理过程中禁用提交按钮:
razor
<button type="submit" disabled="@isSubmitting">
    @(isSubmitting ? "保存中..." : "保存")
</button>

@code {
    private bool isSubmitting;

    private async Task HandleSubmit()
    {
        isSubmitting = true;
        try
        {
            await SaveService.SaveAsync(Model!);
        }
        finally
        {
            isSubmitting = false;
        }
    }
}

Custom Validation CSS

自定义验证CSS

Replace the default
valid
/
invalid
CSS classes:
csharp
public class BootstrapFieldCssClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
        return editContext.IsModified(fieldIdentifier)
            ? (isValid ? "is-valid" : "is-invalid")
            : "";
    }
}
Apply to the form:
csharp
protected override void OnInitialized()
{
    editContext = new EditContext(model);
    editContext.SetFieldCssClassProvider(new BootstrapFieldCssClassProvider());
}
替换默认的
valid
/
invalid
CSS类:
csharp
public class BootstrapFieldCssClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
        return editContext.IsModified(fieldIdentifier)
            ? (isValid ? "is-valid" : "is-invalid")
            : "";
    }
}
应用到表单:
csharp
protected override void OnInitialized()
{
    editContext = new EditContext(model);
    editContext.SetFieldCssClassProvider(new BootstrapFieldCssClassProvider());
}

Don'ts

注意事项

  • Don't use
    @bind
    or
    @oninput
    in Static SSR forms — they require interactivity. Use
    [SupplyParameterFromForm]
    and
    FormName
    .
  • Don't forget
    Model ??= new()
    in
    OnInitialized
    — the model is null on GET, populated on POST.
  • Don't use
    OnSubmit
    together with
    OnValidSubmit
    /
    OnInvalidSubmit
    — they're mutually exclusive.
  • Don't omit
    <DataAnnotationsValidator />
    — validation attributes are silently ignored without it.
  • Don't omit
    FormName
    in SSR when a page has multiple forms — both forms will fire on any submission.
  • Don't use
    InputFile
    in Static SSR — it requires an interactive render mode.
  • Don't use both
    Model
    and
    EditContext
    on an
    EditForm
    — pick one.
  • Don't forget
    <AntiforgeryToken />
    in plain
    <form>
    elements — the server rejects the POST without it.
  • 不要在静态SSR表单中使用
    @bind
    @oninput
    ——它们需要交互能力。请使用
    [SupplyParameterFromForm]
    FormName
  • 不要忘记在
    OnInitialized
    中添加
    Model ??= new()
    ——GET请求时模型为null,POST请求时会被填充。
  • 不要同时使用
    OnSubmit
    OnValidSubmit
    /
    OnInvalidSubmit
    ——它们互斥。
  • 不要省略
    <DataAnnotationsValidator />
    ——没有它,验证属性会被静默忽略。
  • 当页面有多个表单时,不要在SSR中省略
    FormName
    ——否则提交时所有表单都会触发。
  • 不要在静态SSR中使用
    InputFile
    ——它需要交互式渲染模式。
  • 不要在
    EditForm
    上同时使用
    Model
    EditContext
    ——二选一。
  • 不要在原生
    <form>
    元素中省略
    <AntiforgeryToken />
    ——没有它,服务器会拒绝POST请求。