mcpField
C
collect-user-input

collect-user-input

github.com/dotnet开发5.8k3.2k 次安装1 天前更新
代码仓库

关于 collect-user-input

面向 Blazor 开发者的表单模式技能,帮助处理静态 SSR 与交互式渲染模式下的用户输入,涵盖表单设置、绑定、验证、上传和提交。

主要功能

  1. 01SSR 与交互式表单模式
  2. 02EditForm 与提交处理器
  3. 03内置输入组件用法
  4. 04数据注释与自定义验证
  5. 05交互式输入与文件上传
  6. 06表单安全与提交防护

适用场景

  1. 01构建兼容 SSR 和交互模式的表单
  2. 02选择验证与提交处理方式
  3. 03在单页处理多个 SSR 表单
  4. 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 组合使用。

内置输入组件

组件绑定到说明
InputTextstring呈现 <input type="text">
InputTextAreastring呈现 <textarea>
InputNumber<T>int、double、decimal呈现 <input type="number">
InputDate<T>DateTime、DateOnly、DateTimeOffset呈现 <input type="date">
InputCheckboxbool呈现 <input type="checkbox">
InputSelect<T>string、枚举、数值类型呈现 <select>
InputRadioGroup<T>string、枚举、数值类型包含 InputRadio<T> 子组件
InputFileIBrowserFile文件上传 — 仅适用于交互模式

所有输入组件都使用 @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 请求。

评论

支持 Markdown