I'm developing a blazor wasm app and i'm having an hard time understanding how to perform custom async validation on a field. I'm using devexpress's blazor scheduler and i'm trying to validate a custom edit form which supports validation through data annotations. However i have to validate a field based on a value retrieved from a custom api asynchronously. i've tried to create a custom validator but i quickly realized that i cannot inject any registered service in the validation context because of this: https://github.com/dotnet/aspnetcore/issues/11397 so i've tried to use a fluent validator package and created this validator:
public class TlSchedulerAppointmentFormInfoValidator : AbstractValidator<TlSchedulerAppointmentFormInfo>
{
private readonly HttpClient _client;
public TlSchedulerAppointmentFormInfoValidator(HttpClient client)
{
_client = client;
RuleFor(p => p.EndUser)
.MustAsync(async (endUser, cancellation) => await IsEndUserRequired(endUser))
.WithMessage("You must enter your age");
}
private async Task<bool> IsEndUserRequired(string endUser)
{
var setup = await _client.GetFromJsonAsync<Setup>("Setup/Requirements");
bool req = setup?.EndUserObbligatorio ?? false;
return !req || !string.IsNullOrEmpty(endUser);
}
}
but this does not work either because the validator is async and the form closes before the validation occurs, i guess because of this taken from fluent validator docs:
You should not use asynchronous rules when using automatic validation with ASP.NET as ASP.NET’s validation pipeline is not asynchronous. If you use asynchronous rules with ASP.NET’s automatic validation, they will always be run synchronously (10.x and older) or throw an exception (11.x and newer).
Ok then, i tried to (i know i shouldn't do it) GetAwaiter().GetResult() the http call but blazor then gives and exception:
cannot wait on monitors on this runtime validation
So i don't know, i'm really out of options and it seems strange to me to have to go through all these problems for something so simple. Maybe i'm just missing something obvious.
You will need to write your own validation system.
In the standard Blazor EditForm
, validation is driven by calling Validate()
on EditContext
.
The relevant code (from EditContext.cs) is:
public event EventHandler<ValidationRequestedEventArgs>? OnValidationRequested;
public bool Validate()
{
OnValidationRequested?.Invoke(this, ValidationRequestedEventArgs.Empty);
return !GetValidationMessages().Any();
}
Any registered "Validator" has registered an event handler with OnValidationRequested
.
When Validate
runs, it calls all the registered event handlers and then checks to see if there are any registered validation messages in the ValidationMessageStore. If there are messages then something failed validation.
There's no async behaviour coded into this - events are fire and forget.
An async event handler looks like this:
private async void OnValidationRequestedAsync(object? sender, ValidationRequestedEventArgs e)
{
// emulate async behaviour
await Task.Delay(2000);
}
It's return value is a void. As soon as it yields, Validate
continues, sees no validation messages in the store and completes.