mcpField
C
collect-user-input

collect-user-input

github.com/dotnet開発5.8k3.2k 回インストール1 日前に更新
リポジトリ

collect-user-input について

静的 SSR とインタラクティブなレンダリングモードでの入力処理を扱う、Blazor 開発者向けのフォームパターンスキルです。フォーム設定、バインド、検証、アップロード、送信を説明します。

主な機能

  1. 01SSR とインタラクティブフォームのパターン
  2. 02EditForm と送信ハンドラー
  3. 03組み込み入力コンポーネントの指針
  4. 04データ注釈とカスタム検証
  5. 05インタラクティブ入力とファイルアップロード
  6. 06フォームのセキュリティと送信対策

活用シーン

  1. 01SSR とインタラクティブモードに対応するフォーム作成
  2. 02検証と送信処理の選択
  3. 031 ページに複数の 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 で Interactivity Mode と Interactivity Scope を確認します。これにより、適用するフォームパターンが決まります。

モードフォームの仕組み
None(静的 SSR)FormName と [SupplyParameterFromForm] を指定した EditForm。@bind と @onchange は使用しません。
Server@bind-Value を使う EditForm。完全なインタラクティブ性に対応し、リアルタイム検証と動的 UI を利用できます。
WebAssemblyServer と同じですが、サーバーのデータを必要とするバリデーターは API を呼び出す必要があります。
AutoWebAssembly と同じです。コードはブラウザーとサーバーの両方で動作する必要があります。
スコープ影響
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 — 1 つのページに複数のフォームを配置する場合

各フォームには一意の 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; }
フォームの拡張ナビゲーション

ページ全体を再読み込みせず、SPA のようにフォームを送信するには Enhance を追加します:

<EditForm Model="Model" OnValidSubmit="Save" FormName="quick" Enhance>

拡張フォームは fetch で送信し、DOM を更新してスクロール位置を維持します。SSR でも、ページはインタラクティブに操作できるように感じられます。

プレーンな HTML フォーム

SSR で EditForm の代わりに生の <form> を使う場合は、偽造防止トークンを手動で追加します:

<form method="post" @onsubmit="Submit" @formname="raw-form">
    <AntiforgeryToken />
    <input name="Model.Name" value="@Model?.Name" />
    <button type="submit">Send</button>
</form>

EditForm は偽造防止トークンを自動的に含めます。

ファイルのアップロード

InputFile は インタラクティブモードでのみ 動作し、Static 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());
}

やってはいけないこと

  • Static SSR フォームでは @bind や @oninput を使わないでください。これらにはインタラクティブ性が必要です。代わりに [SupplyParameterFromForm] と FormName を使用してください。
  • OnInitialized で Model ??= new() を忘れないでください。GET ではモデルは null で、POST では値が設定されます。
  • OnSubmit と OnValidSubmit/OnInvalidSubmit を併用しないでください。これらは相互に排他的です。
  • <DataAnnotationsValidator /> を省略しないでください。これがないと、検証属性は無視されます。
  • 1 つのページに複数のフォームがある場合、SSR で FormName を省略しないでください。送信時に両方のフォームが実行されます。
  • Static SSR で InputFile を使わないでください。インタラクティブなレンダーモードが必要です。
  • EditForm で Model と EditContext を併用しないでください。どちらか一方を選んでください。
  • プレーンな <form> 要素で <AntiforgeryToken /> を忘れないでください。これがないとサーバーは POST を拒否します。

コメント