แก้ไข

ASP.NET Core Blazor forms validation

Note

This isn't the latest version of this article. For the current release, see the .NET 10 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 10 version of this article.

This article explains how to validate user input in Blazor forms.

For most forms, the simplest and recommended approach is to add data annotations validation attributes to the model and place a DataAnnotationsValidator component in the EditForm. Blazor also supports custom validation through the form's EditContext, either directly in the form component or in a reusable validator component.

Related articles provide more detail:

Validate with data annotations

The following model uses RequiredAttribute and RangeAttribute:

Starship.cs:

using System.ComponentModel.DataAnnotations;

public class Starship
{
    [Required]
    public string? Identifier { get; set; }

    [Range(1, 10, ErrorMessage = "Accommodation must be between 1 and 10.")]
    public int MaximumAccommodation { get; set; }
}

Add the model to an EditForm, include DataAnnotationsValidator, and display errors with ValidationMessage<TValue> or ValidationSummary. The OnValidSubmit callback is invoked only when validation succeeds:

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="Model.Identifier" />
        </label>
        <ValidationMessage For="() => Model.Identifier" />
    </p>

    <p>
        <label>
            Maximum accommodation:
            <InputNumber @bind-Value="Model.MaximumAccommodation" />
        </label>
        <ValidationMessage For="() => Model.MaximumAccommodation" />
    </p>

    <button type="submit">Submit</button>
</EditForm>

@code {
    private Starship Model { get; } = new Starship();

    private void Submit()
    {
        // Process the valid form.
    }
}

For a static SSR form post, assign a unique FormName and receive the posted model with [SupplyParameterFromForm]:

<EditForm Model="Model" FormName="starship" OnValidSubmit="Submit">
    ...
</EditForm>

@code {
    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();
}

For more information about form submission and model binding across render modes, see ASP.NET Core Blazor forms overview and ASP.NET Core Blazor forms binding.

Without a DataAnnotationsValidator component, validation attributes on the model don't participate in the form's validation.

When validation runs

Blazor performs field validation and full-form validation:

  • Field validation runs after a field changes. In an interactive form, this occurs in .NET while the user edits the form.
  • Full-form validation normally runs when EditForm handles submission through OnValidSubmit or OnInvalidSubmit. An OnSubmit handler takes control of validation, as described in Control form submission.

A static SSR form can provide live browser feedback with ASP.NET Core Blazor client-side form validation in static SSR. The form is validated again authoritatively on the server when posted.

A static SSR form is validated on the server when posted and doesn't provide live field validation between requests.

Validation results that identify a member are associated with that field. Results without a member name are associated with the model and appear in a validation summary rather than a field's ValidationMessage component.

Configure data annotations validation

DataAnnotationsValidator always enables DataAnnotations validation for the form. To use the extended validation capabilities provided by the Microsoft.Extensions.Validation package, call the AddValidation extension method in the Program file:

builder.Services.AddValidation();

The AddValidation call registers the package's validation services and activates a source generator that creates validation metadata for discovered model types. The available behavior depends on whether that metadata includes the form's model:

Configuration Behavior
Generated metadata is available Validates nested objects and collections and supports message localization.
Generated metadata isn't available Validates top-level properties, but doesn't validate nested objects or collections and doesn't use the Microsoft.Extensions.Validation message-localization pipeline.
Configuration Behavior
Generated metadata is available Validates nested objects and collections.
Generated metadata isn't available Validates top-level properties only.

The ValidatableTypeAttribute and SkipValidationAttribute APIs are experimental in .NET 10. For details and available workarounds, see Validation in ASP.NET Core.

When using Microsoft.Extensions.Validation, declare model types in C# files (.cs) rather than Razor component files (.razor). The source generator creates validation metadata from C# source and can't include model types declared in Razor components.

For configuration requirements, validation order, custom rules, nested object graphs, and generated metadata, see Validation in ASP.NET Core.

Validate nested object graphs

In .NET 9 or earlier, DataAnnotationsValidator validates top-level model properties but doesn't recursively validate collection or complex-type properties. For recursive validation, use ObjectGraphDataAnnotationsValidator and [ValidateComplexType] from the experimental Microsoft.AspNetCore.Components.DataAnnotations.Validation package:

<EditForm Model="Model" OnValidSubmit="Submit">
    <ObjectGraphDataAnnotationsValidator />
    ...
</EditForm>
public class Starship
{
    [ValidateComplexType]
    public ShipDescription Description { get; set; } =
        new ShipDescription();
}

The package remains experimental in these framework versions.

[CompareProperty] attribute

For .NET 5 or earlier, use the experimental package's ComparePropertyAttribute instead of CompareAttribute. ComparePropertyAttribute associates the validation result with the field consistently during field and full-form validation.

Write model-based custom rules

When built-in attributes can't express a rule, use a custom ValidationAttribute or IValidatableObject. For detailed guidance, see Validation in ASP.NET Core.

Write model-based custom rules

When built-in attributes can't express a rule, use a custom validation attribute or implement IValidatableObject. Both run through DataAnnotationsValidator.

When returning a ValidationResult from a custom attribute, include the validated member name so the result can appear in that field's ValidationMessage component.

Custom attributes can resolve registered services through GetService.

Add validation through EditContext

EditForm creates an EditContext automatically when its Model parameter is assigned. To use validation APIs directly, create the EditContext yourself and assign it to EditContext. Don't assign both Model and EditContext to the same form.

Custom validation commonly uses:

The following interactive-form pattern adds a form-level business rule alongside data annotations validation and rechecks the rule when either relevant field changes:

@implements IDisposable

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    ...
</EditForm>

@code {
    private Starship Model { get; } = new Starship();
    private EditContext editContext = default!;
    private ValidationMessageStore messages = default!;

    protected override void OnInitialized()
    {
        editContext = new EditContext(Model);
        messages = new ValidationMessageStore(editContext);
        editContext.OnValidationRequested += ValidateBusinessRules;
        editContext.OnFieldChanged += ValidateChangedField;
    }

    private void ValidateBusinessRules(
        object? sender, ValidationRequestedEventArgs e)
    {
        messages.Clear();
        ValidateIdentifier();
        editContext.NotifyValidationStateChanged();
    }

    private void ValidateChangedField(
        object? sender, FieldChangedEventArgs e)
    {
        if (e.FieldIdentifier.FieldName != nameof(Starship.Identifier) &&
            e.FieldIdentifier.FieldName != nameof(Starship.MaximumAccommodation))
        {
            return;
        }

        messages.Clear(
            editContext.Field(nameof(Starship.Identifier)));
        ValidateIdentifier();
        editContext.NotifyValidationStateChanged();
    }

    private void ValidateIdentifier()
    {
        if (Model.MaximumAccommodation == 1 &&
            string.IsNullOrWhiteSpace(Model.Identifier))
        {
            messages.Add(
                editContext.Field(nameof(Starship.Identifier)),
                "An identifier is required for a single-occupant ship.");
        }
    }

    private void Submit()
    {
        // Process the valid form.
    }

    public void Dispose()
    {
        editContext.OnValidationRequested -= ValidateBusinessRules;
        editContext.OnFieldChanged -= ValidateChangedField;
    }
}

An OnFieldChanged handler receives the changed field in e.FieldIdentifier. Clear or replace the affected messages and call NotifyValidationStateChanged, as the preceding example demonstrates.

Static SSR doesn't provide live .NET field validation between requests.

For asynchronous full-form validation, call e.AddAsyncValidator from an OnValidationRequested handler. For asynchronous field validation in an interactive form, call EditContext.RegisterAsyncFieldValidator from an OnFieldChanged handler. A new asynchronous validation for the same field supersedes and cancels the previous one.

For model-based asynchronous validation attributes, see Validation in ASP.NET Core. For a complete reusable validator component, see ASP.NET Core Blazor advanced form validation.

For a reusable implementation that encapsulates event subscriptions and its message store, see ASP.NET Core Blazor advanced form validation.

Display validation messages

Use ValidationMessage<TValue> to display messages associated with one field:

<ValidationMessage For="() => Model.Identifier" />

Use ValidationSummary to display messages for the form:

<ValidationSummary />

Assign the summary's Model parameter to restrict it to messages associated with a particular model:

<ValidationSummary Model="Model" />

To inspect current messages in code, call GetValidationMessages:

var allMessages = editContext.GetValidationMessages();
var fieldMessages = editContext.GetValidationMessages(
    editContext.Field(nameof(Starship.Identifier)));

These methods read the current validation state. They don't initiate validation.

Customize validation appearance

Blazor applies CSS classes that represent field and message state:

Element Classes
Input valid or invalid, plus modified after the user edits the field
Validation message validation-message
Validation summary validation-summary-errors or validation-summary-valid

Inputs with asynchronous field validation use pending or faulted, optionally with modified, instead of valid or invalid while the corresponding state applies.

The Blazor project templates include styles for the common valid and invalid classes. Add styles for other classes as needed. ValidationMessage and ValidationSummary also accept arbitrary HTML attributes. Supplying a class attribute replaces the component's default class.

To change the classes applied to input components, derive from FieldCssClassProvider.

using Microsoft.AspNetCore.Components.Forms;

public sealed class BootstrapFieldCssClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(
        EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (!editContext.IsModified(fieldIdentifier))
        {
            return string.Empty;
        }

        return editContext.IsValid(fieldIdentifier)
            ? "is-valid"
            : "is-invalid";
    }
}
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;

public sealed class BootstrapFieldCssClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(
        EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (!editContext.IsModified(fieldIdentifier))
        {
            return string.Empty;
        }

        return editContext.GetValidationMessages(fieldIdentifier).Any()
            ? "is-invalid"
            : "is-valid";
    }
}

A custom FieldCssClassProvider determines the complete class value for each field. If the form uses asynchronous field validation, handle IsValidationPending(fieldIdentifier) and IsValidationFaulted(fieldIdentifier) in the provider when pending or faulted classes are required.

Assign the provider to the form's EditContext:

editContext.SetFieldCssClassProvider(
    new BootstrapFieldCssClassProvider());

For custom input markup, call FieldCssClass to obtain the class selected by the current provider.

Respond to validation state

EditContext exposes the current validation state without initiating validation.

  • Use IsModified(field) or IsModified() to determine whether a field or any field in the form has changed.
  • Use GetValidationMessages(field) or GetValidationMessages() to inspect current field or form messages.

Use IsValid(field) to determine whether a field currently has validation messages.

For a field, the absence of messages can be checked with !editContext.GetValidationMessages(field).Any().

The following example displays custom UI only after a field is modified and invalid:

@{
    var identifier = editContext.Field(nameof(Starship.Identifier));
}

@if (editContext.IsModified(identifier) &&
    !editContext.IsValid(identifier))
{
    <p>Correct the identifier before continuing.</p>
}
@{
    var identifier = editContext.Field(nameof(Starship.Identifier));
}

@if (editContext.IsModified(identifier) &&
    editContext.GetValidationMessages(identifier).Any())
{
    <p>Correct the identifier before continuing.</p>
}

Input components, ValidationMessage, and ValidationSummary update themselves when validation state changes. A component that renders other conditional validation UI should subscribe to OnValidationStateChanged and call StateHasChanged:

private void HandleValidationStateChanged(
    object? sender, ValidationStateChangedEventArgs e) =>
    _ = InvokeAsync(StateHasChanged);

Unsubscribe from OnValidationStateChanged when the component is disposed.

Use IsValidationPending(field) and IsValidationFaulted(field) for asynchronous field validation. The parameterless methods describe form-level ValidateAsync passes and don't aggregate the state of every field.

These states also include asynchronous work performed by DataAnnotationsValidator. An AsyncValidationAttribute applied to a property uses field state during field validation, including the default pending and faulted CSS classes. During ValidateAsync, asynchronous attributes and IAsyncValidatableObject contribute to the form-level state reported by the parameterless methods.

Live pending indicators require an interactive render mode. During a static SSR form post, server-side validation completes before the response is rendered.

Control form submission

EditForm provides three submission callbacks:

Callback Behavior
OnValidSubmit Runs after automatic validation succeeds.
OnInvalidSubmit Runs after automatic validation fails.
OnSubmit Gives the handler control of validation and submission.

OnValidSubmit and OnInvalidSubmit can be used together. Don't combine OnSubmit with either of them.

EditForm uses ValidateAsync before invoking OnValidSubmit or OnInvalidSubmit, so it awaits synchronous and asynchronous validators. When handling OnSubmit, call ValidateAsync before processing the form:

<EditForm EditContext="editContext" OnSubmit="HandleSubmit">
    ...
</EditForm>

@code {
    private async Task HandleSubmit(EditContext editContext)
    {
        if (await editContext.ValidateAsync())
        {
            await SaveAsync();
        }
    }
}

The synchronous Validate method is obsolete in .NET 11. It doesn't await asynchronous validation and throws if a handler attempts to register asynchronous work.

For interactive forms, the form-level pending state can be used to disable submission while ValidateAsync is running:

<button type="submit" disabled="@editContext.IsValidationPending()">
    Save
</button>

When handling OnSubmit, call Validate before processing the form:

<EditForm EditContext="editContext" OnSubmit="HandleSubmit">
    ...
</EditForm>

@code {
    private void HandleSubmit(EditContext editContext)
    {
        if (editContext.Validate())
        {
            Save();
        }
    }
}

Additional resources