Fluent Validation-powered Blazor component for validating standard \
:cyclone: :white_check_mark:
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 :zap: in the application code base:
<EditForm Model="Form">
<FluentValidator></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 <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>();
// Alternatively, use FluentValidation.DependencyInjectionExtensions package (read further down below...)
This effectively allows you, dear programmer, to inject required services to your validation implementations for writing amazing custom validation methods! :fire:
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 .NET Core DI:
<FluentValidator Validator="Validator"></FluentValidator>
@code {
FormModelValidator Validator = new FormModelValidator();
}
dotnet add package FluentValidation.DependencyInjectionExtensions
services.AddValidatorsFromAssemblyContaining<Program>();
Can be used to auto-populate all validators from current application / other project automatically!
FluentValidation offers SetValidator
method for validating nested objects and arrays. Combined with Dependency Injection capability of this component library, a complex form can be validated easily:
public class RootValidator : AbstractValidator<Root>
{
public RootValidator(IValidator<Child> childValidator, IValidator<Item> itemValidator)
{
RuleFor(Q => Q.Child).SetValidator(childValidator);
RuleForEach(Q => Q.ArrayOfItems).SetValidator(itemValidator); // Array, List, IList, ...
}
}
The validators used MUST be registered in the ASP.NET Core service provider!
If for some reason Dependency Injection is not possible, the parameter
ChildValidators
(inlinedDictionary<Type, IValidator>
defining validators used for each children model types) MUST be passed into the component due to technical reasons.
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.Stop)
after RuleFor()
chain,
Or set ValidatorOptions.CascadeMode = CascadeMode.Stop;
on Program.cs
application entry point.
<DataAnnotationValidator>
?<DataAnnotationValidator>
cannot use DI services at the moment...
... but <DataAnnotationValidator>
also cannot do AddModelError()
like Razor Pages. Which rules out validation AFTER form submission!
[ValidationAttribute]
top-level IsValid()
method cannot be async!