![]() |
VOOZH | about |
dotnet add package Blazilla --version 2.4.0
NuGet\Install-Package Blazilla -Version 2.4.0
<PackageReference Include="Blazilla" Version="2.4.0" />
<PackageVersion Include="Blazilla" Version="2.4.0" />Directory.Packages.props
<PackageReference Include="Blazilla" />Project file
paket add Blazilla --version 2.4.0
#r "nuget: Blazilla, 2.4.0"
#:package Blazilla@2.4.0
#addin nuget:?package=Blazilla&version=2.4.0Install as a Cake Addin
#tool nuget:?package=Blazilla&version=2.4.0Install as a Cake Tool
A library for using FluentValidation with Blazor
👁 Nuget version
👁 Build Status
👁 Coverage Status
Blazilla provides seamless integration between FluentValidation and Blazor's EditForm component. This library enables you to use FluentValidation's powerful and flexible validation rules with Blazor forms, supporting both Blazor Server and Blazor WebAssembly applications.
Install the package via NuGet:
dotnet add package Blazilla
Or via Package Manager Console:
Install-Package Blazilla
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int Age { get; set; }
public string? EmailAddress { get; set; }
}
using FluentValidation;
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(p => p.FirstName)
.NotEmpty().WithMessage("First name is required")
.MaximumLength(50).WithMessage("First name cannot exceed 50 characters");
RuleFor(p => p.LastName)
.NotEmpty().WithMessage("Last name is required")
.MaximumLength(50).WithMessage("Last name cannot exceed 50 characters");
RuleFor(p => p.Age)
.GreaterThanOrEqualTo(0).WithMessage("Age must be greater than or equal to 0")
.LessThan(150).WithMessage("Age must be less than 150");
RuleFor(p => p.EmailAddress)
.NotEmpty().WithMessage("Email address is required")
.EmailAddress().WithMessage("Please provide a valid email address");
}
}
Program.cs)using FluentValidation;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// Register FluentValidation validators as singletons for better performance
builder.Services.AddSingleton<IValidator<Person>, PersonValidator>();
var app = builder.Build();
// ... rest of configuration
Program.cs)using FluentValidation;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Register FluentValidation validators as singletons for better performance
builder.Services.AddSingleton<IValidator<Person>, PersonValidator>();
await builder.Build().RunAsync();
@page "/person-form"
<h3>Person Form</h3>
<EditForm Model="@person" OnValidSubmit="@HandleValidSubmit">
<FluentValidator />
<ValidationSummary />
<div class="mb-3">
<label for="firstName" class="form-label">First Name</label>
<InputText id="firstName" class="form-control" @bind-Value="person.FirstName" />
<ValidationMessage For="@(() => person.FirstName)" />
</div>
<div class="mb-3">
<label for="lastName" class="form-label">Last Name</label>
<InputText id="lastName" class="form-control" @bind-Value="person.LastName" />
<ValidationMessage For="@(() => person.LastName)" />
</div>
<div class="mb-3">
<label for="age" class="form-label">Age</label>
<InputNumber id="age" class="form-control" @bind-Value="person.Age" />
<ValidationMessage For="@(() => person.Age)" />
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<InputText id="email" class="form-control" @bind-Value="person.EmailAddress" />
<ValidationMessage For="@(() => person.EmailAddress)" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
@code {
private Person person = new();
private async Task HandleValidSubmit()
{
// Handle successful form submission
Console.WriteLine("Form submitted successfully!");
}
}
Blazor's built-in validation system doesn't natively support asynchronous validation. When using async validation rules with FluentValidation, you need to handle form submission manually to ensure async validation completes before the form is submitted.
Enable asynchronous validation mode and use OnSubmit instead of OnValidSubmit to properly handle async validation:
<EditForm Model="@person" OnSubmit="@HandleSubmit">
<FluentValidator AsyncMode="true" />
<ValidationSummary />
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<InputText id="email" class="form-control" @bind-Value="person.EmailAddress" />
<ValidationMessage For="@(() => person.EmailAddress)" />
</div>
<button type="submit" class="btn btn-primary" disabled="@isSubmitting">
@(isSubmitting ? "Validating..." : "Submit")
</button>
</EditForm>
@code {
private Person person = new();
private bool isSubmitting = false;
private async Task HandleSubmit(EditContext editContext)
{
isSubmitting = true;
StateHasChanged();
try
{
// Use ValidateAsync to ensure all async validation completes
var isValid = await editContext.ValidateAsync();
if (isValid)
{
// Form is valid, proceed with submission
await ProcessValidForm();
}
// If invalid, validation messages will be displayed automatically
}
finally
{
isSubmitting = false;
StateHasChanged();
}
}
private async Task ProcessValidForm()
{
// Handle successful form submission
Console.WriteLine("Form submitted successfully!");
await Task.Delay(1000); // Simulate form processing
}
}
Create a validator with async rules:
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(p => p.EmailAddress)
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Please provide a valid email address")
.MustAsync(async (email, cancellation) =>
{
// Simulate async validation (e.g., database check)
await Task.Delay(500, cancellation);
return !email?.Equals("admin@example.com", StringComparison.OrdinalIgnoreCase) ?? true;
}).WithMessage("This email address is not available");
}
}
Blazor's OnValidSubmit event fires immediately after synchronous validation passes, without waiting for async validation to complete. This can result in forms being submitted with incomplete validation results.
The EditContextExtensions.ValidateAsync() method:
true only when all validation (sync and async) has passedThis ensures that form submission is properly prevented when async validation rules fail.
Important: Always use
OnSubmitinstead ofOnValidSubmitwhen working with async validation rules. TheOnValidSubmitevent doesn't wait for async validation to complete.
Use rule sets to execute specific groups of validation rules:
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
// Default rules (always executed)
RuleFor(p => p.FirstName).NotEmpty();
// Rules in specific rule sets
RuleSet("Create", () =>
{
RuleFor(p => p.EmailAddress)
.NotEmpty()
.EmailAddress();
});
RuleSet("Update", () =>
{
RuleFor(p => p.LastName).NotEmpty();
});
}
}
<FluentValidator RuleSets="@(new[] { "Create" })" />
<FluentValidator AllRules="true" />
Validate complex objects with nested properties:
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public Address? Address { get; set; }
}
public class Address
{
public string? Street { get; set; }
public string? City { get; set; }
public string? PostalCode { get; set; }
}
public class AddressValidator : AbstractValidator<Address>
{
public AddressValidator()
{
RuleFor(a => a.Street).NotEmpty().WithMessage("Street is required");
RuleFor(a => a.City).NotEmpty().WithMessage("City is required");
RuleFor(a => a.PostalCode).NotEmpty().WithMessage("Postal code is required");
}
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.LastName).NotEmpty();
// Validate nested Address object
RuleFor(p => p.Address!)
.SetValidator(new AddressValidator())
.When(p => p.Address is not null);
}
}
<EditForm Model="@person" OnValidSubmit="@HandleValidSubmit">
<FluentValidator />
<InputText @bind-Value="person.FirstName" />
<ValidationMessage For="@(() => person.FirstName)" />
<InputText @bind-Value="person.Address!.Street" />
<ValidationMessage For="@(() => person.Address!.Street)" />
<InputText @bind-Value="person.Address!.City" />
<ValidationMessage For="@(() => person.Address!.City)" />
</EditForm>
Blazilla supports AOT environments such as Blazor WebAssembly AOT and iOS. For the most trim-friendly path, manually register validators or pass the validator directly to the component.
For Blazor WebAssembly AOT, publish with the Blazor WebAssembly AOT property:
dotnet publish -c Release -p:RunAOTCompilation=true
Recommended registration:
using FluentValidation;
builder.Services.AddSingleton<IValidator<Person>, PersonValidator>();
builder.Services.AddSingleton<IValidator<Address>, AddressValidator>();
Recommended direct component usage when convenient:
<FluentValidator Validator="@validator" />
@code {
private readonly PersonValidator validator = new();
}
The assembly-scanning registration helpers remain available, but they are annotated with trim warnings because reflection-based scanning cannot guarantee that validators are preserved after trimming. Avoid assembly scanning for WebAssembly AOT and iOS apps unless you also provide linker preservation for the validator types.
Nested field mapping is preserved in AOT builds. It uses public-property reflection to map Blazor FieldIdentifier values to FluentValidation paths, so trimmed apps must preserve public properties on validation model types when those properties are only accessed through reflection.
IValidator<T> from dependency injection. This works best when validators are manually registered as closed generic services. Passing Validator directly is the most explicit AOT-safe option.AddValidatorsFromAssembly remain available for non-AOT convenience, but they are not trim-safe by themselves. For WebAssembly AOT and iOS, prefer manual registration or add your own linker preservation for scanned validator types.RunAOTCompilation=true; PublishAot=true is for Native AOT scenarios and is not the correct property for Blazor WebAssembly.Pass a validator instance directly instead of using dependency injection:
@code {
private PersonValidator validator = new();
}
<EditForm Model="@person" OnValidSubmit="@HandleValidSubmit">
<FluentValidator Validator="@validator" />
</EditForm>
Implement fine-grained control over which validation rules to execute:
<EditForm Model="@person" OnValidSubmit="@HandleValidSubmit">
<FluentValidator Selector="@customSelector" />
</EditForm>
@code {
private IValidatorSelector customSelector = new CustomValidatorSelector();
public class CustomValidatorSelector : IValidatorSelector
{
public bool CanExecute(IValidationRule rule, string propertyPath, IValidationContext context)
{
// Custom logic to determine if a rule should execute
return true;
}
}
}
The FluentValidator component supports the following parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
Validator |
IValidator? |
null |
The FluentValidation validator instance to use. If not provided, the component will attempt to resolve a validator from the dependency injection container based on the EditForm's model type. |
RuleSets |
IEnumerable<string>? |
null |
Collection of rule set names to execute. Only validation rules within the specified rule sets will be executed. Use this to run targeted validation scenarios (e.g., "Create", "Update", "Profile"). |
AllRules |
bool |
false |
When true, executes all validation rules including those defined within rule sets. When false (default), only executes rules not in any rule set plus any specified in the RuleSets parameter. |
AsyncMode |
bool |
false |
Enables asynchronous validation mode for validators containing async rules (MustAsync, etc.). When enabled, validation will be performed asynchronously and may show a brief delay for async operations like database checks. |
Selector |
IValidatorSelector? |
null |
Custom validator selector implementation for advanced scenarios requiring fine-grained control over which validation rules execute. Allows conditional rule execution based on custom logic, property paths, or validation context. |
OnValidSubmitAsyncMode: Only enable
AsyncMode="true"when your validators contain actual async rules (likeMustAsync). Using async mode with purely synchronous validators introduces unnecessary overhead from async state machine generation and task scheduling, even though the validation logic itself is synchronous
The library automatically converts Blazor's FieldIdentifier objects to FluentValidation property paths to ensure validation messages appear in the correct location. This conversion handles:
Simple Properties
// Blazor FieldIdentifier: { Model = person, FieldName = "FirstName" }
// FluentValidation path: "FirstName"
Nested Properties
// Blazor FieldIdentifier: { Model = address, FieldName = "Street" }
// FluentValidation path: "Address.Street" (when address is a property of person)
Collection Items
// Blazor FieldIdentifier: { Model = phoneNumber, FieldName = "Number" }
// FluentValidation path: "PhoneNumbers[0].Number" (when phoneNumber is item 0 in a collection)
The path conversion is performed internally using object tree analysis to match Blazor's field identification system with FluentValidation's property path conventions. This ensures that validation messages from FluentValidation rules are correctly associated with the corresponding Blazor input components.
The PathResolver automatically traverses your object graph to find the correct property path for validation messages. By default, it skips certain types that shouldn't be traversed:
int, bool, double, etc.)string, Uri, Type)IComponent)Skipping certain types during path resolution is important for several reasons:
For custom scenarios where you need to skip additional types during path resolution, use the PathResolver.AddIgnoredType method. This is useful when you have:
IComponentExample Usage:
// In Program.cs during application startup
using Blazilla;
// Add a single type to ignore
PathResolver.AddIgnoredType(typeof(MyCustomComponent));
// Add multiple types at once (more efficient - rebuilds the internal collection only once)
PathResolver.AddIgnoredType(
typeof(MyCustomComponent),
typeof(MyFrameworkType),
typeof(IMyCustomInterface)); // Interface types - any implementing type will be ignored
Real-World Scenario:
// Custom component base class used throughout your application
public abstract class CustomComponentBase : ComponentBase
{
// Component infrastructure
public RenderFragment? ChildContent { get; set; }
// ... other component properties
}
// During startup, register it to be ignored during path resolution
PathResolver.AddIgnoredType(typeof(CustomComponentBase));
// Now any type inheriting from CustomComponentBase will be skipped during traversal,
// preventing unnecessary performance overhead and potential traversal into component internals
Performance Tip: When adding multiple types, pass them all in a single call to AddIgnoredType rather than calling it multiple times. This rebuilds the internal frozen set only once instead of once per type.
Thread Safety: The AddIgnoredType method is thread-safe and uses a high-performance frozen set internally. However, it's recommended to call this method only during application startup (in Program.cs) before any validation occurs to ensure consistent behavior across your application.
Important: Blazor's built-in form validation system has inherent limitations with asynchronous validation:
OnValidSubmit Event Timing: The OnValidSubmit event fires immediately after synchronous validation passes, without waiting for any asynchronous validation operations to complete.
EditContext.Validate() Behavior: The standard EditContext.Validate() method only performs synchronous validation and doesn't trigger or wait for async validation rules.
Workaround for Async Validation:
When using validators with async rules (MustAsync, custom async validators), you must:
AsyncMode="true" on the FluentValidator componentOnSubmit instead of OnValidSubmitEditContextExtensions.ValidateAsync() to ensure async validation completesThis limitation is a fundamental aspect of Blazor's validation architecture and affects all validation libraries, not just FluentValidation integrations.
If you're currently using Blazored.FluentValidation, migrating to Blazilla is straightforward. The libraries share similar APIs and functionality, with some key improvements in Blazilla.
| Feature | Blazored.FluentValidation | Blazilla |
|---|---|---|
| Component Name | <FluentValidationValidator /> |
<FluentValidator /> |
| Validator Discovery | Automatic assembly scanning, DI, or explicit pass | Requires DI registration or explicit pass |
| Async Validation Support | Limited, requires workarounds | Built-in with AsyncMode parameter |
| Nested Object Validation | Limited (doesn't support all scenarios) | Fully supported with improved path resolution |
| Rule Sets | Supported via RuleSet parameter |
Supported via RuleSets and AllRules parameters |
| Custom Validator Selector | Not supported | Supported via Selector parameter |
| Performance | Poor, degrades with nested objects | High, optimized with compiled expression trees |
| DI Registration Required | No (automatic assembly scanning fallback) | Yes (or pass via Validator parameter) |
Remove the Blazored.FluentValidation package:
dotnet remove package Blazored.FluentValidation
Add Blazilla:
dotnet add package Blazilla
Replace:
using Blazored.FluentValidation;
With:
using Blazilla;
Replace the component name in your Razor files:
Before (Blazored.FluentValidation):
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
<FluentValidationValidator />
<ValidationSummary />
</EditForm>
After (Blazilla):
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
<FluentValidator />
<ValidationSummary />
</EditForm>
If you're using rule sets, update the parameter name:
Before:
<FluentValidationValidator RuleSet="Create,Update" />
After:
<FluentValidator RuleSets="@(new[] { "Create", "Update" })" />
Or use the newer pattern for executing all rules:
<FluentValidator AllRules="true" />
Blazilla has improved async validation support. Update your components:
Before (Blazored.FluentValidation - required manual handling):
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
<FluentValidationValidator @ref="validator" />
<button type="submit" disabled="@isValidating">Submit</button>
</EditForm>
@code {
private FluentValidationValidator? validator;
private bool isValidating;
private async Task HandleValidSubmit()
{
// Manual async validation handling
if (validator != null)
{
isValidating = true;
await Task.Delay(100); // Wait for async validation
isValidating = false;
}
// Process form
}
}
After (Blazilla - built-in support):
<EditForm Model="@model" OnSubmit="@HandleSubmit">
<FluentValidator AsyncMode="true" />
<button type="submit" disabled="@isSubmitting">Submit</button>
</EditForm>
@code {
private bool isSubmitting;
private async Task HandleSubmit(EditContext editContext)
{
isSubmitting = true;
try
{
// Built-in async validation handling
var isValid = await editContext.ValidateAsync();
if (isValid)
{
// Process form
await ProcessForm();
}
}
finally
{
isSubmitting = false;
}
}
}
Important Change: Blazored.FluentValidation automatically scans assemblies to find validators - no setup required. Blazilla requires validators to be either registered in DI or passed via the Validator parameter.
Before (Blazored.FluentValidation - automatic assembly scanning):
// No registration needed - Blazored.FluentValidation automatically
// scans assemblies and finds validators at runtime
After (Blazilla - explicit registration required):
// Manual registration (simple but tedious for many validators)
builder.Services.AddSingleton<IValidator<Person>, PersonValidator>();
builder.Services.AddSingleton<IValidator<Company>, CompanyValidator>();
builder.Services.AddSingleton<IValidator<Address>, AddressValidator>();
// ... register all validators used in your application
Registration Options:
To simplify registration of multiple validators, you can use one of these approaches:
Option 1: FluentValidation's Automatic Registration
// Install: dotnet add package FluentValidation.DependencyInjectionExtensions
// Scans assembly and registers all validators
services.AddValidatorsFromAssemblyContaining<PersonValidator>();
Option 2: Scrutor for Assembly Scanning
// Install: dotnet add package Scrutor
services.Scan(scan => scan
.FromAssemblyOf<PersonValidator>()
.AddClasses(classes => classes.AssignableTo(typeof(IValidator<>)))
.AsImplementedInterfaces()
.WithSingletonLifetime());
Option 3: Injectio for Attribute-Based Registration
// Install: dotnet add package Injectio
// Add attribute to each validator class
[RegisterSingleton<IValidator<Person>>]
public class PersonValidator : AbstractValidator<Person>
{
// validator implementation
}
Option 4: Pass Validators Directly
// No DI registration needed - pass validator instance to component
<FluentValidator Validator="@(new PersonValidator())" />
Note: Use singleton lifetime since validators are stateless and thread-safe. Options 1-3 simplify registration when you have many validators.
No Automatic Assembly Scanning: This is the biggest breaking change. Blazored.FluentValidation automatically scans assemblies at runtime to discover and use validators without any configuration. Blazilla does not support automatic assembly scanning - you must explicitly provide validators.
You have two options with Blazilla:
Validator parameter on each componentIf you have many validators, you'll need to add DI registrations for each one in your Program.cs.
No validator found error
No validator found for model type MyModel. To use FluentValidator, register a validator for this model type or pass one directly to the Validator parameter.
Solution: Ensure your validator is registered in the DI container:
builder.Services.AddSingleton<IValidator<MyModel>, MyModelValidator>();
EditContext parameter missing
FluentValidator requires a cascading parameter of type EditContext.
Solution: Ensure the component is placed inside an EditForm:
<EditForm Model="@model">
<FluentValidator />
</EditForm>
Invalid form submits when using AsyncMode
When using AsyncMode="true", forms may submit even when async validation fails if you're using OnValidSubmit instead of OnSubmit.
Problem: OnValidSubmit fires immediately after synchronous validation passes, without waiting for async validation to complete.
Solution: Use OnSubmit with EditContextExtensions.ValidateAsync():
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
<FluentValidator AsyncMode="true" />
</EditForm>
<EditForm Model="@model" OnSubmit="@HandleSubmit">
<FluentValidator AsyncMode="true" />
</EditForm>
@code {
private async Task HandleSubmit(EditContext editContext)
{
// Wait for all async validation to complete
var isValid = await editContext.ValidateAsync();
if (isValid)
{
// Only proceed if validation passed
await ProcessForm();
}
}
}
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 3 NuGet packages that depend on Blazilla:
| Package | Downloads |
|---|---|
|
Nabs.Launchpad.Ui.Shell.Blazor.Sf
Package Description |
|
|
Empezar.BlazorWebClient
Package Description |
|
|
IdentitySuite
A ready-to-use OpenId Connect Server based on Openiddict with Microsoft Identity and a comprehensive management shell. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.4.0 | 3,441 | 6/4/2026 |
| 2.3.2 | 9,557 | 5/12/2026 |
| 2.3.1 | 12,118 | 4/21/2026 |
| 2.3.0 | 49,820 | 2/6/2026 |
| 2.2.2 | 6,438 | 1/30/2026 |
| 2.2.1 | 10,139 | 1/16/2026 |
| 2.2.0 | 1,540 | 1/14/2026 |
| 2.1.0 | 6,970 | 12/17/2025 |
| 2.0.1 | 13,531 | 11/14/2025 |
| 2.0.0 | 917 | 11/12/2025 |
| 1.1.1-beta.1 | 200 | 11/3/2025 |
| 1.1.0 | 3,776 | 10/19/2025 |
| 1.0.1 | 2,139 | 9/27/2025 |
| 1.0.0 | 407 | 9/14/2025 |
| 1.0.0-alpha.5 | 156 | 9/14/2025 |