collect-user-input
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCollect User Input
收集用户输入
Step 1 — Read the Project's AGENTS.md
步骤1 — 阅读项目的AGENTS.md
Check for Interactivity Mode and Interactivity Scope. This determines which form patterns apply:
AGENTS.md| Mode | Form mechanism |
|---|---|
| None (Static SSR) | |
| Server | |
| WebAssembly | Same as Server, but validators needing server data must call APIs. |
| Auto | Same as WebAssembly — code must work in both browser and server. |
| Scope | Impact |
|---|---|
| Global | All forms are interactive. |
| Per-page | Forms in static pages use |
查看中的交互模式和交互范围,这将决定适用的表单模式:
AGENTS.md| 模式 | 表单机制 |
|---|---|
| None (Static SSR) | 搭配 |
| Server | 搭配 |
| WebAssembly | 与Server模式相同,但需要服务器数据的验证器必须调用API。 |
| Auto | 与WebAssembly模式相同——代码需同时兼容浏览器和服务器环境。 |
| 范围 | 影响 |
|---|---|
| Global | 所有表单均为交互式。仅当明确将页面设置为静态SSR时才需要 |
| Per-page | 静态页面中的表单使用 |
EditForm Setup
EditForm 配置
EditFormModelEditContextEditFormModelEditContextModel-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: identifies the form,
FormNamebinds POST data,[SupplyParameterFromForm]initializes on GET.??= - In interactive: provides two-way binding,
@bind-Valueis ignored,[SupplyParameterFromForm]is harmless.FormName
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绑定POST数据,[SupplyParameterFromForm]在GET请求时初始化模型。??= - 在交互式模式中:提供双向绑定,
@bind-Value会被忽略,[SupplyParameterFromForm]无负面影响。FormName
EditContext-based (advanced)
基于EditContext(进阶)
Use when you need programmatic field tracking, dynamic validation rules, or manual calls:
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">当需要程序化字段跟踪、动态验证规则或手动调用时使用:
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
提交处理器
| Handler | Fires when | Use when |
|---|---|---|
| Validation passes | Standard forms with |
| Validation fails | Need custom handling for invalid state |
| Always — validation is manual | Using |
OnSubmitOnValidSubmitOnInvalidSubmit| 处理器 | 触发时机 | 使用场景 |
|---|---|---|
| 验证通过时 | 使用 |
| 验证失败时 | 需要对无效状态进行自定义处理 |
| 始终触发——需手动验证 | 自行使用 |
OnSubmitOnValidSubmitOnInvalidSubmitBuilt-in Input Components
内置输入组件
| Component | Binds to | Notes |
|---|---|---|
| | Renders |
| | Renders |
| | Renders |
| | Renders |
| | Renders |
| | Renders |
| | Wraps |
| | File upload — interactive modes only |
All input components use for binding. Always wrap text in a or use / attributes for accessibility.
@bind-Value<label>idfor| 组件 | 绑定类型 | 说明 |
|---|---|---|
| | 渲染为 |
| | 渲染为 |
| | 渲染为 |
| | 渲染为 |
| | 渲染为 |
| | 渲染为 |
| | 包裹 |
| | 文件上传——仅支持交互式模式 |
所有输入组件均使用进行绑定。为了 accessibility,始终将文本包裹在中,或使用/属性。
@bind-Value<label>idforInputSelect 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 inside — without it, annotation attributes are silently ignored.
<DataAnnotationsValidator />EditFormDisplay errors with:
- — all errors in a list
<ValidationSummary /> - — per-field inline errors
<ValidationMessage For="() => Model!.FieldName" />
在模型上定义验证规则:
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,或未设置的单页面)中渲染的场景。
@rendermodeSupplyParameterFromForm
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 is required. On GET the property is null — creates the model. On POST the framework populates it — preserves the posted values.
??=OnInitialized??=??=在表单提交时将POST数据绑定到属性:
csharp
[SupplyParameterFromForm]
private ContactModel? Contact { get; set; }
protected override void OnInitialized() => Contact ??= new();关键注意事项: 中的是必需的。在GET请求时属性为null——会创建模型;在POST请求时框架会填充该属性——会保留提交的值。
OnInitialized??=??=??=FormName — multiple forms on one page
FormName — 单页面多表单
Each form needs a unique :
FormNamerazor
<EditForm Model="Search" OnSubmit="DoSearch" FormName="search">...</EditForm>
<EditForm Model="Contact" OnValidSubmit="SaveContact" FormName="contact">...</EditForm>Match to its form:
[SupplyParameterFromForm]csharp
[SupplyParameterFromForm(FormName = "search")]
private SearchModel? Search { get; set; }
[SupplyParameterFromForm(FormName = "contact")]
private ContactModel? Contact { get; set; }每个表单需要唯一的:
FormNamerazor
<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 for SPA-like form submissions without full page reload:
Enhancerazor
<EditForm Model="Model" OnValidSubmit="Save" FormName="quick" Enhance>Enhanced forms submit via , patch the DOM, and preserve scroll position. The page stays interactive-feeling even in SSR.
fetch添加以实现类SPA的表单提交,无需整页刷新:
Enhancerazor
<EditForm Model="Model" OnValidSubmit="Save" FormName="quick" Enhance>增强型表单通过提交、修补DOM并保留滚动位置。即使在SSR中,页面也能保持交互式体验。
fetchPlain HTML forms
纯HTML表单
When using raw instead of in SSR, add the antiforgery token manually:
<form>EditFormrazor
<form method="post" @onsubmit="Submit" @formname="raw-form">
<AntiforgeryToken />
<input name="Model.Name" value="@Model?.Name" />
<button type="submit">Send</button>
</form>EditForm在SSR中使用原生而非时,需手动添加防伪令牌:
<form>EditFormrazor
<form method="post" @onsubmit="Submit" @formname="raw-form">
<AntiforgeryToken />
<input name="Model.Name" value="@Model?.Name" />
<button type="submit">提交</button>
</form>EditFormFile Upload
文件上传
InputFilerazor
<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 to increase. Large files stream over the circuit.
OpenReadStream(maxAllowedSize) - 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
}
}
}InputFilerazor
<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 / CSS classes:
validinvalidcsharp
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());
}替换默认的/ CSS类:
validinvalidcsharp
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 or
@bindin Static SSR forms — they require interactivity. Use@oninputand[SupplyParameterFromForm].FormName - Don't forget in
Model ??= new()— the model is null on GET, populated on POST.OnInitialized - Don't use together with
OnSubmit/OnValidSubmit— they're mutually exclusive.OnInvalidSubmit - Don't omit — validation attributes are silently ignored without it.
<DataAnnotationsValidator /> - Don't omit in SSR when a page has multiple forms — both forms will fire on any submission.
FormName - Don't use in Static SSR — it requires an interactive render mode.
InputFile - Don't use both and
Modelon anEditContext— pick one.EditForm - Don't forget in plain
<AntiforgeryToken />elements — the server rejects the POST without it.<form>
- 不要在静态SSR表单中使用或
@bind——它们需要交互能力。请使用@oninput和[SupplyParameterFromForm]。FormName - 不要忘记在中添加
OnInitialized——GET请求时模型为null,POST请求时会被填充。Model ??= new() - 不要同时使用和
OnSubmit/OnValidSubmit——它们互斥。OnInvalidSubmit - 不要省略——没有它,验证属性会被静默忽略。
<DataAnnotationsValidator /> - 当页面有多个表单时,不要在SSR中省略——否则提交时所有表单都会触发。
FormName - 不要在静态SSR中使用——它需要交互式渲染模式。
InputFile - 不要在上同时使用
EditForm和Model——二选一。EditContext - 不要在原生元素中省略
<form>——没有它,服务器会拒绝POST请求。<AntiforgeryToken />