Validating a Blazor Form on the first render

 
 
  • Gérald Barré

Laurent Kempé asked on DevApps, a French .NET community, about how to validate a form on the first render in ASP.NET Core Blazor. This could be useful, for instance, when you load draft data, and you want to immediately show errors. The default behavior in Blazor is to validate fields when the value changes. So, you must tweak it to validate the form on the first render.

#How validation works in Blazor

Blazor stores the state of the form in an EditContext instance. The <EditForm> component creates an EditContext implicitly. You can also create your own EditContext if you need more control over the validation lifecycle.

The EditContext exposes multiple methods and events to handle the validation:

  • OnFieldChanged: An event that is raised when a field value changes. Validators use this event to validate a field as soon as the value changes.
  • OnValidationRequested: An event that is raised when validation is requested using the Validate method.
  • OnValidationStateChanged: An event that is raised when the validation state has changed. This event is mainly used by the <ValidationSummary> and <ValidationMessage> components to update the validation message when the status changes.
  • bool Validate(): Raise the OnValidationRequested event and returns true if the form is valid, false otherwise.

When the validation is requested, you can use a ValidationMessageStore object to store error messages. Components such as <ValidationSummary> and <ValidationMessage> use this object to get the validation messages and display them.

Here's an example of how to use the EditContext and ValidationMessageStore to validate a form:

Razor
<EditForm EditContext="editContext">
    <InputText @bind-Value="model.Value" />
    <ValidationMessage For="() => model.Value" />

    <button type="button" @onclick="() => editContext.Validate()">Validate Form</button>
</EditForm>

@code {
    Model model;
    EditContext editContext;
    ValidationMessageStore messageStore;

    protected override void OnInitialized()
    {
        model = new Model();
        editContext = new EditContext(model);
        messageStore = new ValidationMessageStore(editContext);
        editContext.OnValidationRequested += OnValidationRequested;
    }

    private void OnValidationRequested(object sender, ValidationRequestedEventArgs e)
    {
        messageStore.Clear();
        if (string.IsNullOrEmpty(model.Value))
        {
            messageStore.Add(() => model.Value, "Value is required");
        }
    }
}

Instead of implementing the logic manually, you can use the <DataAnnotationsValidator> component to validate the form using data annotation attributes. This component subscribes to the EditorContext events to validate the form when a value changes.

Razor
<EditForm Model="model">
    <DataAnnotationsValidator />
    <InputText @bind-Value="model.Value" />
    <ValidationMessage For="() => model.Value" />
</EditForm>

We now understand how to use the EditContext to validate the form. Let's see how to validate a form on the first render!

#Method 1: Calling Validate in OnAfterRender

The first way to validate the form is to call Validate in the OnAfterRender method. You can get a reference to the EditForm using @ref to get access to the EditContext. Then, you can call the Validate method manually. The editForm field is set at the first render, so you need to use OnAfterRender to call Validate:

Razor
<EditForm Model="model" @ref="editForm">
    <DataAnnotationsValidator />
    <InputText @bind-Value="model.Value" />
    <ValidationMessage For="() => model.Value" />
</EditForm>

@code {
    Model model = new();
    EditForm editForm; // Set by @ref during Render

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            editForm.EditContext.Validate();
        }

        base.OnAfterRender(firstRender);
    }
}

The downside of this method is that you need 2 renders to display error messages. The first render is to render the form and set the reference to the EditForm. The second render is to display the error messages.

#Method 2: Using an EditContext

The second solution is to manually create an EditContext instance to validate the form in the Initialization phase. As the component is not yet rendered, the validators are not registered. So, if you call Validate no validation occurs. You need to manually register the validator using EnableDataAnnotationsValidation().

Razor
@implements IDisposable

<EditForm EditContext="editContext">
    <InputText @bind-Value="model.Value" />
    <ValidationMessage For="() => model.Value" />
</EditForm>

@code {
    Model model;
    EditContext editContext;
    IDisposable dataAnnotationRegistration;

    protected override void OnInitialized()
    {
        model = new Model();
        editContext = new EditContext(model);
        dataAnnotationRegistration = editContext.EnableDataAnnotationsValidation();
        editContext.Validate();
    }

    public void Dispose()
    {
        dataAnnotationRegistration?.Dispose();
    }
}

There is a little more code, but this time there is only one render call to display the form and the error messages.

The second method is ok, but you need to write the same logic for each form. If you have a lot of forms, you can create a new component to handle the validation logic.

InitialValidator.cs (C#)
public class InitialValidator : ComponentBase
{
    // Get the EditContext from the parent component (EditForm)
    [CascadingParameter]
    private EditContext CurrentEditContext { get; set; }

    protected override void OnParametersSet()
    {
        CurrentEditContext?.Validate();
    }
}
Razor
<EditForm Model="model">
    <DataAnnotationsValidator />
    <InitialValidator />

    <InputText @bind-Value="model.Value" />
    <ValidationMessage For="() => model.Value" />
</EditForm>

@code {
    Model model = new();
}

This code is reusable and the component is rendered only once. So, it's easier to add initial validation, and there is no flickering issues 😃

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub