ASP.NET Core Blazor forms overview
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
This article explains how to use forms in Blazor.
Input components and forms
The Blazor framework supports forms and provides built-in input components:
- Bound to an object or model that can use data annotations.
- HTML forms with the
<form>
element. - EditForm components.
- HTML forms with the
- Built-in input components.
- An EditForm component bound to an object or model that can use data annotations.
- Built-in input components.
Note
Unsupported ASP.NET Core validation features are covered in the Unsupported validation features section.
The Microsoft.AspNetCore.Components.Forms namespace provides:
- Classes for managing form elements, state, and validation.
- Access to built-in Input* components.
A project created from the Blazor project template includes the namespace in the app's _Imports.razor
file, which makes the namespace available to the app's Razor components.
Standard HTML forms are supported. Create a form using the normal HTML <form>
tag and specify an @onsubmit
handler for handling the submitted form request.
StarshipPlainForm.razor
:
@page "/starship-plain-form"
@inject ILogger<StarshipPlainForm> Logger
<form method="post" @onsubmit="Submit" @formname="starship-plain-form">
<AntiforgeryToken />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);
public class Starship
{
public string? Id { get; set; }
}
}
@page "/starship-plain-form"
@inject ILogger<StarshipPlainForm> Logger
<form method="post" @onsubmit="Submit" @formname="starship-plain-form">
<AntiforgeryToken />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);
public class Starship
{
public string? Id { get; set; }
}
}
In the preceding StarshipPlainForm
component:
- The form is rendered where the
<form>
element appears. The form is named with the@formname
directive attribute, which uniquely identifies the form to the Blazor framework. - The model is created in the component's
@code
block and held in a public property (Model
). The[SupplyParameterFromForm]
attribute indicates that the value of the associated property should be supplied from the form data. Data in the request that matches the property's name is bound to the property. - The InputText component is an input component for editing string values. The
@bind-Value
directive attribute binds theModel.Id
model property to the InputText component's Value property. - The
Submit
method is registered as a handler for the@onsubmit
callback. The handler is called when the form is submitted by the user.
Important
Always use the @formname
directive attribute with a unique form name.
Blazor enhances page navigation and form handling by intercepting the request in order to apply the response to the existing DOM, preserving as much of the rendered form as possible. The enhancement avoids the need to fully load the page and provides a much smoother user experience, similar to a single-page app (SPA), although the component is rendered on the server. For more information, see ASP.NET Core Blazor routing and navigation.
Streaming rendering is supported for plain HTML forms.
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
The preceding example includes antiforgery support by including an AntiforgeryToken component in the form. Antiforgery support is explained further in the Antiforgery support section of this article.
To submit a form based on another element's DOM events, for example oninput
or onblur
, use JavaScript to submit the form (submit
(MDN documentation)).
Instead of using plain forms in Blazor apps, a form is typically defined with Blazor's built-in form support using the framework's EditForm component. The following Razor component demonstrates typical elements, components, and Razor code to render a webform using an EditForm component.
A form is defined using the Blazor framework's EditForm component. The following Razor component demonstrates typical elements, components, and Razor code to render a webform using an EditForm component.
Starship1.razor
:
@page "/starship-1"
@inject ILogger<Starship1> Logger
<EditForm Model="Model" OnSubmit="Submit" FormName="Starship1">
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);
public class Starship
{
public string? Id { get; set; }
}
}
@page "/starship-1"
@inject ILogger<Starship1> Logger
<EditForm Model="Model" OnSubmit="Submit" FormName="Starship1">
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);
public class Starship
{
public string? Id { get; set; }
}
}
In the preceding Starship1
component:
- The EditForm component is rendered where the
<EditForm>
element appears. The form is named with the@formname
directive attribute, which uniquely identifies the form to the Blazor framework. - The model is created in the component's
@code
block and held in a public property (Model
). The property is assigned to the EditForm.Model parameter. The[SupplyParameterFromForm]
attribute indicates that the value of the associated property should be supplied from the form data. Data in the request that matches the property's name is bound to the property. - The InputText component is an input component for editing string values. The
@bind-Value
directive attribute binds theModel.Id
model property to the InputText component's Value property. - The
Submit
method is registered as a handler for the OnSubmit callback. The handler is called when the form is submitted by the user.
Important
Always use the @formname
directive attribute with a unique form name.
Blazor enhances page navigation and form handling for EditForm components. For more information, see ASP.NET Core Blazor routing and navigation.
Streaming rendering is supported for EditForm.
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
@page "/starship-1"
@inject ILogger<Starship1> Logger
<EditForm Model="Model" OnSubmit="Submit">
<InputText @bind-Value="Model!.Id" />
<button type="submit">Submit</button>
</EditForm>
@code {
public Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit()
{
Logger.LogInformation("Model.Id = {Id}", Model?.Id);
}
public class Starship
{
public string? Id { get; set; }
}
}
In the preceding Starship1
component:
- The EditForm component is rendered where the
<EditForm>
element appears. - The model is created in the component's
@code
block and held in a private field (model
). The field is assigned to the EditForm.Model parameter. - The InputText component is an input component for editing string values. The
@bind-Value
directive attribute binds theModel.Id
model property to the InputText component's Value property†. - The
Submit
method is registered as a handler for the OnSubmit callback. The handler is called when the form is submitted by the user.
†For more information on property binding, see ASP.NET Core Blazor data binding.
In the next example, the preceding component is modified to create the form in the Starship2
component:
- OnSubmit is replaced with OnValidSubmit, which processes assigned event handler if the form is valid when submitted by the user.
- A ValidationSummary component is added to display validation messages when the form is invalid on form submission.
- The data annotations validator (DataAnnotationsValidator component†) attaches validation support using data annotations:
- If the
<input>
form field is left blank when theSubmit
button is selected, an error appears in the validation summary (ValidationSummary component‡) ("The Id field is required.
") andSubmit
is not called. - If the
<input>
form field contains more than ten characters when theSubmit
button is selected, an error appears in the validation summary ("Id is too long.
").Submit
is not called. - If the
<input>
form field contains a valid value when theSubmit
button is selected,Submit
is called.
- If the
†The DataAnnotationsValidator component is covered in the Validator component section. ‡The ValidationSummary component is covered in the Validation Summary and Validation Message components section.
Starship2.razor
:
@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship2">
<DataAnnotationsValidator />
<ValidationSummary />
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
<button type="submit">Submit</button>
</EditForm>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship2">
<DataAnnotationsValidator />
<ValidationSummary />
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
<button type="submit">Submit</button>
</EditForm>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Id = {Id}", Model?.Id);
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText @bind-Value="Model!.Id" />
<button type="submit">Submit</button>
</EditForm>
@code {
public Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit()
{
Logger.LogInformation("Id = {Id}", Model?.Id);
}
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
Handle form submission
The EditForm provides the following callbacks for handling form submission:
- Use OnValidSubmit to assign an event handler to run when a form with valid fields is submitted.
- Use OnInvalidSubmit to assign an event handler to run when a form with invalid fields is submitted.
- Use OnSubmit to assign an event handler to run regardless of the form fields' validation status. The form is validated by calling EditContext.Validate in the event handler method. If Validate returns
true
, the form is valid.
Clear a form or field
Reset a form by clearing its model back its default state, which can be performed inside or outside of an EditForm's markup:
<button @onclick="ClearForm">Clear form</button>
...
private void ClearForm() => Model = new();
Alternatively, use an explicit Razor expression:
<button @onclick="@(() => Model = new())">Clear form</button>
Reset a field by clearing its model value back to its default state:
<button @onclick="ResetId">Reset Identifier</button>
...
private void ResetId() => Model!.Id = string.Empty;
Alternatively, use an explicit Razor expression:
<button @onclick="@(() => Model!.Id = string.Empty)">Reset Identifier</button>
There's no need to call StateHasChanged in the preceding examples because StateHasChanged is automatically called by the Blazor framework to rerender the component after an event handler is invoked. If an event handler isn't used to invoke the methods that clear a form or field, then developer code should call StateHasChanged to rerender the component.
Antiforgery support
Antiforgery services are automatically added to Blazor apps when AddRazorComponents is called in the Program
file.
The app uses Antiforgery Middleware by calling UseAntiforgery in its request processing pipeline in the Program
file. UseAntiforgery is called after the call to UseRouting. If there are calls to UseRouting and UseEndpoints, the call to UseAntiforgery must go between them. A call to UseAntiforgery must be placed after calls to UseAuthentication and UseAuthorization.
The AntiforgeryToken component renders an antiforgery token as a hidden field, and the [RequireAntiforgeryToken]
attribute enables antiforgery protection. If an antiforgery check fails, a 400 - Bad Request
response is thrown and the form isn't processed.
For forms based on EditForm, the AntiforgeryToken component and [RequireAntiforgeryToken]
attribute are automatically added to provide antiforgery protection.
For forms based on the HTML <form>
element, manually add the AntiforgeryToken component to the form:
<form method="post" @onsubmit="Submit" @formname="starshipForm">
<AntiforgeryToken />
<input id="send" type="submit" value="Send" />
</form>
@if (submitted)
{
<p>Form submitted!</p>
}
@code{
private bool submitted = false;
private void Submit() => submitted = true;
}
Warning
For forms based on either EditForm or the HTML <form>
element, antiforgery protection can be disabled by passing required: false
to the [RequireAntiforgeryToken]
attribute. The following example disables antiforgery and is not recommended for public apps:
@using Microsoft.AspNetCore.Antiforgery
@attribute [RequireAntiforgeryToken(required: false)]
For more information, see ASP.NET Core Blazor authentication and authorization.
Mitigate overposting attacks
Statically-rendered server-side forms, such as those typically used in components that create and edit records in a database with a form model, can be vulnerable to an overposting attack, also known as a mass assignment attack. An overposting attack occurs when a malicious user issues an HTML form POST to the server that processes data for properties that aren't part of the rendered form and that the developer doesn't wish to allow users to modify. The term "overposting" literally means that the malicious user has over-POSTed with the form.
Overposting isn't a concern when the model doesn't include restricted properties for create and update operations. However, it's important to keep overposting in mind when working with static SSR-based Blazor forms that you maintain.
To mitigate overposting, we recommend using a separate view model/data transfer object (DTO) for the form and database with create (insert) and update operations. When the form is submitted, only properties of the view model/DTO are used by the component and C# code to modify the database. Any extra data included by a malicious user is discarded, so the malicious user is prevented from conducting an overposting attack.
Enhanced form handling
Enhance navigation for form POST requests with the Enhance parameter for EditForm forms or the data-enhance
attribute for HTML forms (<form>
):
<EditForm ... Enhance ...>
...
</EditForm>
<form ... data-enhance ...>
...
</form>
Unsupported: You can't set enhanced navigation on a form's ancestor element to enable enhanced form handling.
<div ... data-enhance ...>
<form ...>
<!-- NOT enhanced -->
</form>
</div>
Enhanced form posts only work with Blazor endpoints. Posting an enhanced form to non-Blazor endpoint results in an error.
To disable enhanced form handling:
- For an EditForm, remove the Enhance parameter from the form element (or set it to
false
:Enhance="false"
). - For an HTML
<form>
, remove thedata-enhance
attribute from form element (or set it tofalse
:data-enhance="false"
).
Blazor's enhanced navigation and form handing may undo dynamic changes to the DOM if the updated content isn't part of the server rendering. To preserve the content of an element, use the data-permanent
attribute.
In the following example, the content of the <div>
element is updated dynamically by a script when the page loads:
<div data-permanent>
...
</div>
To disable enhanced navigation and form handling globally, see ASP.NET Core Blazor startup.
For guidance on using the enhancedload
event to listen for enhanced page updates, see ASP.NET Core Blazor routing and navigation.
Examples
Examples don't adopt enhanced form handling for form POST requests, but all of the examples can be updated to adopt the enhanced features by following the guidance in the Enhanced form handling section.
Examples use the target-typed new
operator, which was introduced with C# 9.0 and .NET 5. In the following example, the type isn't explicitly stated for the new
operator:
public ShipDescription ShipDescription { get; set; } = new();
If using C# 8.0 or earlier (ASP.NET Core 3.1), modify the example code to state the type to the new
operator:
public ShipDescription ShipDescription { get; set; } = new ShipDescription();
Components use nullable reference types (NRTs), and the .NET compiler performs null-state static analysis, both of which are supported in .NET 6 or later. For more information, see Migrate from ASP.NET Core 5.0 to 6.0.
If using C# 9.0 or earlier (.NET 5 or earlier), remove the NRTs from the examples. Usually, this merely involves removing the question marks (?
) and exclamation points (!
) from the types in the example code.
The .NET SDK applies implicit global using
directives to projects when targeting .NET 6 or later. The examples use a logger to log information about form processing, but it isn't necessary to specify an @using
directive for the Microsoft.Extensions.Logging namespace in the component examples. For more information, see .NET project SDKs: Implicit using directives.
If using C# 9.0 or earlier (.NET 5 or earlier), add @using
directives to the top of the component after the @page
directive for any API required by the example. Find API namespaces through Visual Studio (right-click the object and select Peek Definition) or the .NET API browser.
To demonstrate how forms work with data annotations validation, example components rely on System.ComponentModel.DataAnnotations API. If you wish to avoid an extra line of code in components that use data annotations, make the namespace available throughout the app's components with the imports file (_Imports.razor
):
@using System.ComponentModel.DataAnnotations
Form examples reference aspects of the Star Trek universe. Star Trek is a copyright ©1966-2023 of CBS Studios and Paramount.
Client-side validation requires a circuit
In Blazor Web Apps, client-side validation requires an active Blazor SignalR circuit. Client-side validation isn't available to forms in components that have adopted static server-side rendering (static SSR). Forms that adopt static SSR are validated on the server after the form is submitted.
Unsupported validation features
All of the data annotation built-in validators are supported in Blazor except for the [Remote]
validation attribute.
jQuery validation isn't supported in Razor components. We recommend any of the following approaches:
- Follow the guidance in ASP.NET Core Blazor forms validation for either:
- Server-side validation in a Blazor Web App that adopts an interactive render mode.
- Client-side validation in a standalone Blazor Web Assembly app.
- Use native HTML validation attributes (see Client-side form validation (MDN documentation)).
- Adopt a third-party validation JavaScript library.
For statically-rendered forms on the server, a new mechanism for client-side validation is under consideration for .NET 10 in late 2025. For more information, see Create server rendered forms with client validation using Blazor without a circuit (dotnet/aspnetcore
#51040).
Additional resources
- ASP.NET Core Blazor file uploads
- Secure a hosted ASP.NET Core Blazor WebAssembly app with Microsoft Entra ID
- Secure a hosted ASP.NET Core Blazor WebAssembly app with Azure Active Directory B2C
- Secure a hosted ASP.NET Core Blazor WebAssembly app with Identity Server
- Blazor samples GitHub repository (
dotnet/blazor-samples
) (how to download) - ASP.NET Core GitHub repository (
dotnet/aspnetcore
) forms test assets
ASP.NET Core