关于 collect-user-input
面向 Blazor 开发者的表单模式技能,帮助处理静态 SSR 与交互式渲染模式下的用户输入,涵盖表单设置、绑定、验证、上传和提交。
主要功能
- 01SSR 与交互式表单模式
- 02EditForm 与提交处理器
- 03内置输入组件用法
- 04数据注释与自定义验证
- 05交互式输入与文件上传
- 06表单安全与提交防护
适用场景
- 01构建兼容 SSR 和交互模式的表单
- 02选择验证与提交处理方式
- 03在单页处理多个 SSR 表单
- 04添加交互式筛选或文件上传
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill collect-user-input安装后在 Claude Code 中输入/collect-user-input确认已加载。
详细介绍
收集用户输入
步骤 1 — 阅读项目的 AGENTS.md
检查 AGENTS.md 中的 交互模式和交互范围。这决定适用哪些表单模式:
| 模式 | 表单机制 |
|---|---|
| None(静态 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 设置
EditForm 必须设置 Model 或 EditContext 其中之一 — 不能同时设置。
基于 Model(默认)
<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
}
}
此模式同时适用于 SSR 和交互模式:
- 在 SSR 中:
FormName用于标识表单,[SupplyParameterFromForm]用于绑定 POST 数据,??=用于在 GET 时初始化。 - 在交互模式中:
@bind-Value提供双向绑定,[SupplyParameterFromForm]会被忽略,FormName不会造成影响。
基于 EditContext(高级)
当需要以编程方式跟踪字段、使用动态验证规则或手动调用 EditContext.Validate() 时使用:
private EditContext? editContext;
private EmployeeModel model = new();
protected override void OnInitialized()
{
editContext = new EditContext(model);
}
<EditForm EditContext="editContext" OnValidSubmit="HandleSubmit" FormName="employee">
提交处理程序
| 处理程序 | 触发条件 | 适用场景 |
|---|---|---|
OnValidSubmit | 验证通过 | 使用 DataAnnotationsValidator 的标准表单 |
OnInvalidSubmit | 验证失败 | 需要自定义处理无效状态 |
OnSubmit | 始终触发 — 需手动进行验证 | 自行使用 EditContext.Validate() |
OnSubmit 不能与 OnValidSubmit 或 OnInvalidSubmit 组合使用。
内置输入组件
| 组件 | 绑定到 | 说明 |
|---|---|---|
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 进行绑定。为保证无障碍访问,始终使用 <label> 包裹文本,或使用 id/for 属性。
使用枚举值的 InputSelect
<InputSelect @bind-Value="Model!.Status">
<option value="">-- Select --</option>
@foreach (var value in Enum.GetValues<OrderStatus>())
{
<option value="@value">@value</option>
}
</InputSelect>
InputRadioGroup
<InputRadioGroup @bind-Value="Model!.Priority">
@foreach (var p in Enum.GetValues<Priority>())
{
<label>
<InputRadio Value="p" /> @p
</label>
}
</InputRadioGroup>
验证
数据注解
在模型上定义验证规则:
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" />— 在字段旁内联显示错误
自定义验证器组件
用于需要与服务器往返的验证(唯一性检查、业务规则):
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();
}
}
在表单中使用:
<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
}
}
响应输入变化(仅限交互模式)
@bind:after
在绑定值变化后运行逻辑:
<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;
}
}
用于实时筛选的 @oninput
<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();
}
}
SSR 专用模式
这些模式适用于表单在静态 SSR 中呈现的情况(模式 = None,或未使用 @rendermode 的按页模式)。
SupplyParameterFromForm
在提交表单时将 POST 数据绑定到属性:
[SupplyParameterFromForm]
private ContactModel? Contact { get; set; }
protected override void OnInitialized() => Contact ??= new();
重要: OnInitialized 中的 ??= 是必需的。GET 时属性为 null — ??= 会创建模型。POST 时,框架会填充该属性 — ??= 会保留已提交的值。
FormName — 一个页面上的多个表单
每个表单都需要唯一的 FormName:
<EditForm Model="Search" OnSubmit="DoSearch" FormName="search">...</EditForm>
<EditForm Model="Contact" OnValidSubmit="SaveContact" FormName="contact">...</EditForm>
将 [SupplyParameterFromForm] 与其表单匹配:
[SupplyParameterFromForm(FormName = "search")]
private SearchModel? Search { get; set; }
[SupplyParameterFromForm(FormName = "contact")]
private ContactModel? Contact { get; set; }
表单增强导航
添加 Enhance,实现类似 SPA 的表单提交,无需完整重新加载页面:
<EditForm Model="Model" OnValidSubmit="Save" FormName="quick" Enhance>
增强型表单通过 fetch 提交、更新 DOM,并保留滚动位置。即使在 SSR 中,页面也能保持交互体验。
纯 HTML 表单
在 SSR 中使用原始 <form> 而不是 EditForm 时,请手动添加防伪令牌:
<form method="post" @onsubmit="Submit" @formname="raw-form">
<AntiforgeryToken />
<input name="Model.Name" value="@Model?.Name" />
<button type="submit">Send</button>
</form>
EditForm 会自动包含防伪令牌。
文件上传
InputFile 仅适用于交互模式,不适用于静态 SSR。
<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.
}
}
流大小限制:
- 服务器: 默认 SignalR 消息大小约为 30 KB。调用
OpenReadStream(maxAllowedSize)可提高限制。大文件会通过电路流式传输。 - WebAssembly: 文件在浏览器中读取。没有 SignalR 限制,但受内存限制。
上传多个文件时:
<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
}
}
}
防止重复提交
处理期间禁用提交按钮:
<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;
}
}
}
自定义验证 CSS
替换默认的 valid/invalid CSS 类:
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")
: "";
}
}
应用到表单:
protected override void OnInitialized()
{
editContext = new EditContext(model);
editContext.SetFieldCssClassProvider(new BootstrapFieldCssClassProvider());
}
注意事项
- 不要在静态 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 请求。