Blazor Data Binding, Forms, and Validation
|
This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14. This section’s bibliography lists the reference material consulted while preparing these pages. |
Blazor binds C# state to markup with @bind and validates it with EditForm/EditContext. This page assumes
the component model already covered.
One-way and two-way binding
A plain @expression is one-way (state to markup); @bind-Value (or its shorthand @bind) is two-way,
listening for a change event and writing the new value back:
<p>Hello, @name</p> @* one-way *@
<input @bind="name" /> @* two-way: writes back on the element's default event (change) *@
@bind:event, @bind:after, @bind:get/@bind:set
<input @bind="name" @bind:event="oninput" /> @* update on every keystroke *@
<input @bind="name" @bind:after="Search" /> @* run Search() after the value commits *@
<input @bind:get="Query" @bind:set="OnQueryChanged" /> @* split read/write for custom logic *@
@code {
private string name = "";
private string Query { get; set; } = "";
private void Search() => results = catalog.Search(name);
private async Task OnQueryChanged(string value)
{
Query = value;
await SaveDraftAsync(value);
}
}
-
@bind:eventoverrides the triggering DOM event (default isonchange;oninputfires per keystroke). -
@bind:afterruns a method after the bound value is assigned — convenient for "debounced search" or "recompute a derived value" without manually wiring an event handler. -
@bind:get/@bind:setsplit a two-way binding into an explicit getter and setter, useful when the setter needs to beasyncor must transform the incoming value.
Format strings work the same way as before: <input @bind="startDate" @bind:format="yyyy-MM-dd" />.
EventCallback vs. Action
Prefer EventCallback / EventCallback<T> over a plain Action/Action<T> delegate for parameters a
component invokes as an event: EventCallback integrates with Blazor’s rendering so the invoking component
re-renders after the callback returns (including awaiting an async handler), which a raw delegate does not do
automatically.
@code { [Parameter] public EventCallback<ProductDto> OnBuy { get; set; } }
<button @onclick="() => OnBuy.InvokeAsync(Product)">Buy</button>
EditForm and EditContext
EditForm builds an EditContext over a model and tracks field modification/validation state for its
descendants:
<EditForm Model="Input" OnValidSubmit="Save">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText @bind-Value="Input.Name" />
<ValidationMessage For="() => Input.Name" />
<InputNumber @bind-Value="Input.Price" />
<InputDate @bind-Value="Input.ReleaseOn" />
<InputCheckbox @bind-Value="Input.InStock" />
<InputSelect @bind-Value="Input.CategoryId">
@foreach (var c in Categories) { <option value="@c.Id">@c.Name</option> }
</InputSelect>
<button type="submit">Save</button>
</EditForm>
@code {
private ProductInput Input = new();
private Task Save() => ProductApi.CreateAsync(Input);
}
OnValidSubmit (and OnInvalidSubmit) fire only after validation runs; use plain OnSubmit to control that
yourself. Injecting EditContext directly (instead of Model) is the way to share one context across nested
components or add custom validation logic. See
ASP.NET Core Blazor forms overview.
Built-in input components
InputText, InputTextArea, InputNumber, InputSelect, InputCheckbox, InputDate, InputRadio /
InputRadioGroup, InputFile, and, since .NET 10, InputHidden — each binds a model field and participates
in EditContext validation:
<InputRadioGroup @bind-Value="Input.Size">
<InputRadio Value="'S'" /> Small
<InputRadio Value="'M'" /> Medium
<InputRadio Value="'L'" /> Large
</InputRadioGroup>
<InputFile OnChange="OnFileSelected" multiple />
<InputHidden @bind-Value="Input.CorrelationId" />
Validation
public sealed class ProductInput
{
[Required, StringLength(100)] public string Name { get; set; } = "";
[Range(0.01, 100000)] public decimal Price { get; set; }
}
<DataAnnotationsValidator /> wires System.ComponentModel.DataAnnotations attributes into the EditContext;
<ValidationSummary /> lists every error, <ValidationMessage For="…"> shows one field’s error. Add a
custom validation attribute (class MustBeEvenAttribute : ValidationAttribute) for reusable rules, or
subscribe to EditContext.OnValidationRequested for ad hoc cross-field logic.
[ValidatableType] for declarative validation of nested objects and collections — previouslyDataAnnotationsValidator only validated the top-level model’s own properties:
[ValidatableType]
public sealed class OrderInput
{
[Required] public string Customer { get; set; } = "";
public List<OrderLineInput> Lines { get; set; } = [];
}
public sealed class OrderLineInput
{
[Required] public string Sku { get; set; } = "";
[Range(1, 100)] public int Quantity { get; set; }
}
Marking OrderInput (and transitively validated types) [ValidatableType] makes DataAnnotationsValidator
walk into Lines and validate each OrderLineInput, which previously required a hand-written
IValidatableObject implementation. See
ASP.NET Core Blazor forms validation.
SSR form handling
A Static SSR page has no interactive circuit to bind against, so its EditForm posts a normal HTTP form
submission; recover the values with [SupplyParameterFromForm] and protect the endpoint with antiforgery:
@page "/products/create"
@attribute [RenderModeStatic]
<EditForm Model="Input" method="post" OnValidSubmit="Save" FormName="create-product">
<AntiforgeryToken />
<DataAnnotationsValidator />
<InputText @bind-Value="Input.Name" />
<button type="submit">Save</button>
</EditForm>
@code {
[SupplyParameterFromForm] private ProductInput Input { get; set; } = new();
private async Task Save()
{
await ProductApi.CreateAsync(Input);
Nav.NavigateTo("/products");
}
}
Antiforgery validation is enabled for Blazor Web App form posts by default via AddAntiforgery and
UseAntiforgery; <AntiforgeryToken /> embeds the token in the rendered form. See
ASP.NET Core Blazor forms overview and
Security Hardening for antiforgery/CSRF in general.