dotnet/skills

collect-user-input

Build forms, validate data, and react to user input in Blazor.

Voir la source
Document Skill original

Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.

Collect User Input

Step 1 — Read the Project's 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.
ServerEditForm 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.

EditForm Setup

EditForm requires either Model or EditContext — never both.

Model-based (default)

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.

EditContext-based (advanced)

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">

Submit Handlers

HandlerFires whenUse when
OnValidSubmitValidation passesStandard forms with DataAnnotationsValidator
OnInvalidSubmitValidation failsNeed custom handling for invalid state
OnSubmitAlways — validation is manualUsing EditContext.Validate() yourself

OnSubmit cannot combine with OnValidSubmit/OnInvalidSubmit.

Built-in Input Components

ComponentBinds toNotes
InputTextstringRenders <input type="text">
InputTextAreastringRenders <textarea>
InputNumber<T>int, double, decimalRenders <input type="number">
InputDate<T>DateTime, DateOnly, DateTimeOffsetRenders <input type="date">
InputCheckboxboolRenders <input type="checkbox">
InputSelect<T>string, enums, numeric typesRenders <select>
InputRadioGroup<T>string, enums, numeric typesWraps InputRadio<T> children
InputFileIBrowserFileFile 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.

InputSelect with enum values

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

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

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
    }
}

React to Input Changes (Interactive Only)

@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;
    }
}

@oninput for real-time filtering

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();
    }
}

SSR-Specific Patterns

These apply when the form renders in Static SSR (mode = None, or per-page without @rendermode).

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.

FormName — multiple forms on one page

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; }

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.

Plain HTML forms

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.

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
        }
    }
}

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;
        }
    }
}

Custom Validation 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());
}

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.
du même dépôt

Autres Skills

Tous les Skills
dotnet
Communauté

dotnet-webapi

Guides creation and modification of ASP.NET Core Web API endpoints with correct HTTP semantics, OpenAPI metadata, and error handling. USE FOR: adding new API endpoints (controllers or minimal APIs), wiring up OpenAPI/Swagger, creating .http test files, setting up global error handling middleware. DO NOT USE FOR: general C coding style, EF Core data access or query optimization (use optimizing-ef-core-queries), frontend/Blazor work, gRPC services, or SignalR hubs.

installations
3
GitHub Stars
5,5 k
Mis à jour
23 sept.
dotnet
Communauté

test-anti-patterns

Audit a test file or suite; produce a severity-ranked diagnostic report. ALWAYS USE for tests that verify nothing, missing/tautological assertions, swallowed/broad exceptions, flaky/order-dependent tests, duplication, or magic values. Polyglot. DO NOT USE for direct edits: writing-mstest-tests owns supplied MSTest assertions/attributes/lifecycle; code-testing-agent owns new tests. Exclude running tests, migration, assertion metrics (assertion-quality), raw .NET coverage collection (run-tests), non-.NET coverage collection/analysis (native tooling), project-wide .NET coverage/CRAP (coverage-analysis), named-target .NET CRAP (crap-score), behavioral/pseudo-mutation gaps (test-gap-analysis), test-mix/ happy-vs-error classification and trait distributions (test-tagging), or the testsmells.org catalog (test-smell-detection).

installations
4
GitHub Stars
5,5 k
Mis à jour
23 sept.
dotnet
Communauté

binlog-failure-analysis

Analyze MSBuild binary logs to diagnose build failures. USE FOR: build errors that are unclear from console output, diagnosing cascading failures across multi-project builds, tracing MSBuild target execution order, and generally any MSBuild build issues. Requires an existing .binlog file. DO NOT USE FOR: generating binlogs (use binlog-generation), non-MSBuild build systems.

installations
1
GitHub Stars
5,5 k
Mis à jour
22 sept.
dotnet
Communauté

coverage-analysis

Activation requires either supplied .NET coverage reports/percentages/line, branch, or condition metrics, or an explicit request to collect .NET coverage for analysis. USE FOR: interpreting that evidence, including Cobertura data, partial conditions, plateaus, target arithmetic, project-wide coverage-backed CRAP, and coverage-backed refactoring safety. Analyze supplied reports directly without rerunning tests or installing tools. DO NOT USE FOR: requests with neither coverage evidence nor explicit coverage-collection intent, including hypothetical change-survival questions (use test-gap-analysis); CRAP or refactoring safety for one named target (use crap-score); or requests owned by test-tagging, find-untested-sources, test-anti-patterns, run-tests, or code-testing-agent.

installations
1
GitHub Stars
5,5 k
Mis à jour
22 sept.