Validating a Blazor Form on the first render
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 theValidate
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 theOnValidationRequested
event and returnstrue
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:
<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.
<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
:
<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()
.
@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.
#Method 3 (recommended): Creating a new component
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.
public class InitialValidator : ComponentBase
{
// Get the EditContext from the parent component (EditForm)
[CascadingParameter]
private EditContext CurrentEditContext { get; set; }
protected override void OnParametersSet()
{
CurrentEditContext?.Validate();
}
}
<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
- ASP.NET Core Blazor forms and validation
- Validating an input on keypress instead of on change in Blazor
Do you have a question or a suggestion about this post? Contact me!