Form validation is an important part of building reliable web applications. When users submit information through a registration form, checkout page, or profile update screen, the application needs to verify that the submitted data meets the required rules.
Blazor provides built-in validation components such as DataAnnotationsValidator, ValidationMessage, and ValidationSummary. These components make it easy to validate user input before submitting a form.
However, client-side validation is not enough for every scenario.
Consider a registration form where a user enters an email address. The value may have a valid email format, but the database might already contain an account with the same address. The browser cannot reliably determine this without communicating with the server.
The server must perform the actual validation and return an appropriate error response. Blazor then needs to display that error next to the corresponding form field.
In this article, we will explore how to handle server-side validation errors in Blazor forms, use EditContext and ValidationMessageStore, and build a reusable validation component that displays errors returned by an ASP.NET Core API.
Understanding Client-Side and Server-Side Validation
Before implementing the solution, it is important to understand the difference between client-side and server-side validation.
Client-side validation checks user input before sending a request to the server. It is useful for identifying common problems such as missing values, invalid email formats, and incorrect input lengths.
Server-side validation checks the submitted information using application rules, databases, and other trusted resources.
For example, consider a user registration form.
Validation rule | Validation location |
|---|---|
Name is required | Client and server |
Email format is valid | Client and server |
Email already exists | Server |
Phone number is already registered | Server |
User is eligible for registration | Server |
Required fields are missing | Client and server |
Client-side validation improves the user experience, but it should never be treated as the final authority.
A user can bypass browser validation or send an HTTP request without using the Blazor interface. Therefore, the server must validate every request independently.
Understanding EditContext and ValidationMessageStore
Blazor provides an EditContext object to manage the state of a form.
It tracks information such as:
The model associated with the form.
Modified fields.
Current validation messages.
Validation state changes.
When an EditForm uses a model, Blazor automatically creates an EditContext.
However, when server-side validation errors arrive from an API, Blazor needs a way to add those messages to the existing validation state.
This is where ValidationMessageStore becomes useful.
ValidationMessageStore allows developers to add and remove validation messages associated with specific fields.
For example, if the API returns an error stating that an email address is already registered, the application can associate that message with the Email property.
The ValidationMessage component can then display the error beside the corresponding input field.
Implementing Server-Side Validation Errors in Blazor
Let's create a practical registration form.
The application will use the following flow:
The user enters registration information.
Blazor performs client-side validation.
The form sends valid information to an ASP.NET Core API.
The API checks business rules and database constraints.
If validation fails, the API returns field-specific errors.
Blazor adds those errors to the form's validation state.
The user sees the messages beside the relevant fields.
Step 1. Create the Registration Model
First, create a model containing the registration information.
using System.ComponentModel.DataAnnotations;
public sealed class RegisterUserModel
{
[Required(ErrorMessage = "Name is required.")]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "Email is required.")]
[EmailAddress(ErrorMessage = "Enter a valid email address.")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Phone number is required.")]
public string PhoneNumber { get; set; } = string.Empty;
}Data annotation attributes define basic validation rules.
These rules can run in the Blazor form and can also be applied by the ASP.NET Core API when the server uses the same validation model.
Step 2. Create the ASP.NET Core API
Next, create an API endpoint that validates the submitted registration information.
For this example, assume that a user repository is available to check existing email addresses.
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private readonly IUserRepository _repository;
public UsersController(IUserRepository repository)
{
_repository = repository;
}
[HttpPost("register")]
public async Task<IActionResult> Register(
RegisterUserModel model,
CancellationToken cancellationToken)
{
bool emailExists = await _repository.EmailExistsAsync(
model.Email,
cancellationToken);
if (emailExists)
{
ModelState.AddModelError(
nameof(model.Email),
"This email address is already registered."
);
return ValidationProblem(ModelState);
}
bool phoneExists = await _repository.PhoneExistsAsync(
model.PhoneNumber,
cancellationToken);
if (phoneExists)
{
ModelState.AddModelError(
nameof(model.PhoneNumber),
"This phone number is already registered."
);
return ValidationProblem(ModelState);
}
return Ok(new
{
Message = "Registration completed successfully."
});
}
}The important part is ModelState.AddModelError.
It associates a validation message with a specific property.
The ValidationProblem method converts the ModelState errors into a standard HTTP 400 validation response.
The response contains an Errors object with field names and their corresponding error messages.
For example, the API might return:
{
"type": "https://tools.ietf.org/html/rfc9110",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Email": [
"This email address is already registered."
]
}
}This structure allows the Blazor application to identify which field needs to display the error.
Step 3. Create a Reusable Custom Validation Component
Now create a component named CustomValidation.razor.
This component will receive server-side validation errors and add them to the current EditContext.
@implements IDisposable
@code {
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
"CustomValidation requires an EditContext.");
}
messageStore = new ValidationMessageStore(
CurrentEditContext);
CurrentEditContext.OnFieldChanged += HandleFieldChanged;
}
public void DisplayErrors(
Dictionary<string, string[]> errors)
{
if (CurrentEditContext is null || messageStore is null)
return;
messageStore.Clear();
foreach (var error in errors)
{
var fieldIdentifier = new FieldIdentifier(
CurrentEditContext.Model,
error.Key);
messageStore.Add(
fieldIdentifier,
error.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
public void ClearErrors()
{
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
private void HandleFieldChanged(
object? sender,
FieldChangedEventArgs e)
{
messageStore?.Clear(e.FieldIdentifier);
CurrentEditContext?.NotifyValidationStateChanged();
}
public void Dispose()
{
if (CurrentEditContext is not null)
{
CurrentEditContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
Join the conversation! Your thoughts help the community grow.