Skip to content

LostPolygon/FluentValidation.Blazor

 
 

Repository files navigation

FluentValidation.Blazor

Fluent Validation-powered Blazor component for validating standard <EditForm> 🌀 ✅

Build Status NuGet Accelist.FluentValidation.Blazor package in test feed in Azure Artifacts

Install

dotnet add package FluentValidation
dotnet add package Accelist.FluentValidation.Blazor

Getting Started

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);
    }
}

Inlined Validator

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();
}

Asynchronous Warning

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!

Repeated Remote Call Warning ⚠️

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) after RuleFor() chain,

  • Or set ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure; on Program.cs application entry point.

FluentValidation.DependencyInjectionExtensions

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)

Making use of InjectValidator or GetServiceProvider is only supported when using the automatic MVC integration.

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);
    }
}

Why Not Just Use <DataAnnotationValidator>?

  1. <DataAnnotationValidator> cannot use DI services at the moment...

  2. ... but <DataAnnotationValidator> also cannot do AddModelError() like Razor Pages. Which rules out validation AFTER form submission!

  3. [ValidationAttribute] top-level IsValid() method cannot be async!

About

Fluent Validation-powered Blazor component for validating standard <EditForm> 🌀 ✅

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C# 99.4%
  • HTML 0.6%