Fluent Validation-powered Blazor component for validating standard <EditForm> 🌀 ✅
dotnet add package FluentValidation
dotnet add package Accelist.FluentValidation.Blazor
This library is a direct replacement to the default Blazor <DataAnnotationValidator>
with zero configuration required ⚡ in the application code base:
<EditForm Model="Form">
<FluentValidation.FluentValidator></FluentValidation.FluentValidator>
<div class="form-group">
<label for="email">Email</label>
<InputText id="email" type="email" class="form-control" @bind-Value="Form.Email"></InputText>
<ValidationMessage For="() => Form.Email"></ValidationMessage>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">
<i class="fas fa-chevron-up"></i>
Submit
</button>
</div>
</EditForm>
@code {
FormModel Form = new FormModel();
}
public class FormModel
{
public string Email { set; get; }
}
public class FormModelValidator : AbstractValidator<FormModel>
{
public FormModelValidator()
{
RuleFor(Q => Q.Email).NotEmpty().EmailAddress().MaximumLength(255);
}
}
The <FluentValidation.FluentValidator>
component automatically detects the Model
data type used by the parent <EditForm>
then attempts to acquire the corresponding FluentValidation.IValidator<T>
for that model data type.
For this reason, in addition to coding the usual FluentValidation.AbstractValidator<T> Fluent Validation implementation
, you are required to register the FluentValidation.IValidator<T>
implementation in the Startup.cs
Service Provider (Dependency Injection):
services.AddTransient<IValidator<CreateAccountFormModel>, CreateAccountFormModelValidator>();
This effectively allows you, dear programmer, to inject required services to your validation implementations for writing amazing custom validation methods! 🔥
public class FormModelValidator : AbstractValidator<FormModel>
{
readonly AppDbContext DB;
readonly IServiceProvider SP;
public FormModelValidator(AppDbContext db, IServiceProvider sp)
{
this.DB = db;
this.SP = sp;
RuleFor(Q => Q.Email).NotEmpty().EmailAddress().MaximumLength(255)
.Must(BeUniqueEmail).WithMessage("Email address is already registered.");
}
bool BeUniqueEmail(string email)
{
var exist = DB.Account.Where(Q => Q.Email == email).Any();
return (exist == false);
}
}
Validator
parameter may also be passed directly to the component to inline the AbstractValidator
implementation instead of relying on DI mechanism:
<FluentValidator Validator="Validator"></FluentValidator>
@code {
FormModelValidator Validator = new FormModelValidator();
}
Due to buggy UI when using async void
on EditContext.OnValidationRequested
and EditContext.OnFieldChanged
event handler (i.e. CSS not updating, to be reported to ASP.NET Core issue repo), the validator is run synchronously.
This should not stop you from writing async validation methods because Fluent Validation will force run async methods synchronously.
Just something to keep in mind when coding!
By default, Fluent Validation does NOT short-circuit the validation chain on first error.
This may cause performance issues when a validation logic hits the database / remote service multiple times in short burst due to validation triggers on field change!
To reduce the impact of repeated custom validation calls, use:
-
.Cascade(CascadeMode.StopOnFirstFailure)
afterRuleFor()
chain, -
Or set
ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;
onProgram.cs
application entry point.
dotnet add package FluentValidation.DependencyInjectionExtensions
services.AddValidatorsFromAssemblyContaining<Program>();
Can be used to auto-populate all validators from current application / other project automatically!
However, InjectValidator
extension method cannot be used because: (quoting @JeremySkinner comment)
That said, the built-in SetValidator
method can still be used with an injected IValidator<T>
object instead:
public class FormModelValidator : AbstractValidator<FormModel>
{
public FormModelValidator(IValidator<SomeProperty> somePropertyValidator)
{
RuleFor(Q => Q.SomeProperty).SetValidator(somePropertyValidator);
}
}
-
<DataAnnotationValidator>
cannot use DI services at the moment... -
... but
<DataAnnotationValidator>
also cannot doAddModelError()
like Razor Pages. Which rules out validation AFTER form submission! -
[ValidationAttribute]
top-levelIsValid()
method cannot be async!