ASP.NET Core Blazor forms overview (original) (raw)

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:

Note

Unsupported ASP.NET Core validation features are covered in the Unsupported validation features section.

The Microsoft.AspNetCore.Components.Forms namespace provides:

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:

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 that when POSTing a form, only DOM updates inside the form's handlers are streamed (for example, @onsubmit). Updates inside OnInitializedAsync are only streamed for GET requests. For more information, see Allow streaming the loading phase of POST responses (dotnet/aspnetcore #50994).

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).

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:

Important

Always use the FormName property 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 that when POSTing a form, only DOM updates inside the form's handlers are streamed (for example, OnValidSubmit). Updates inside OnInitializedAsync are only streamed for GET requests. For more information, see Allow streaming the loading phase of POST responses (dotnet/aspnetcore #50994).

@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:

†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:

†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:

Clear a form or field

Reset a form by clearing its model back to 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:

Blazor's enhanced navigation and form handling 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 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 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 in .NET 5 to .NET 6.

If using C# 9 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 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:

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