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">
<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! ๐ฅ
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)
afterRuleFor()
chain, -
Or set
ValidatorOptions.CascadeMode = CascadeMode.Stop;
onProgram.cs
application entry point.
-
<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!