Blazor forms
dotnet add package Nullean.Entities.Blazor
Put <ConstraintValidator> inside an EditForm and give it the model's constraint set. Violations then show where Blazor shows any validation message: under the field in
<ValidationMessage For="..."> and in <ValidationSummary>, and OnValidSubmit only runs when there are none.
<EditForm Model="Model" OnValidSubmit="Save">
<ConstraintValidator Constraints="SignUp.Constraints" />
<InputText @bind-Value="Model.Email" />
<ValidationMessage For="() => Model.Email" />
<InputText type="password" @bind-Value="Model.Password" />
<InputText type="password" @bind-Value="Model.Confirm" />
<ValidationMessage For="() => Model.Confirm" />
<ValidationSummary />
<button type="submit">Sign up</button>
</EditForm>
SignUp is an ordinary mutable form model with [Constraint] fields, so SignUp.Constraints is generated. For a model whose rules live in a companion, pass the companion's set:
<ConstraintValidator Constraints="ContactRules.All" />. The model type is inferred from the set.
- On submit the whole model is checked, and every violation is shown.
- When a field changes the set runs again (a constraint checks the whole value, not one property) and that field gets its current messages. On the other fields a message that no longer
holds is removed, but nothing new is added to a field the user has not reached yet. So fixing
Passwordclears "must match the password" onConfirm, and typing an email does not flag an empty name. - Where a message goes: a violation is shown on the field named by
Violation.Member(new FieldIdentifier(model, member)). A violation without a member is about the whole model, and only<ValidationSummary>shows it. - Errors only, by default.
MinimumSeverity="Severity.Warning"also shows warnings, andSeverity.Hintshows everything. Blazor counts every message as invalid, so a shown warning also blocksOnValidSubmit. Time="..."passes aTimeProviderto rules that read the clock (NotInFuture,Always((x, now) => ...)).
Blazor's own <DataAnnotationsValidator /> runs a model's IValidatableObject.Validate. When a constraint type implements IValidatableObject (see DataAnnotations),
that validator already reports your constraints, and you need no extra package. Use ConstraintValidator instead when:
- the rules live in a companion (
ContactRules.All) or a set you build at runtime, not on the model type; - you do not want the model to implement
IValidatableObject; - you want per-field updates:
DataAnnotationsValidatorre-runs only property attributes when a field changes, so model-level rules (IValidatableObject) appear and clear only on submit, while this component re-checks the set on every change and updates the changed field; - you want warnings or hints shown, or a
TimeProvider.
Both can sit in one form: DataAnnotationsValidator for attributes the generator does not turn into constraints, ConstraintValidator for the set. Do not use both for the same rules, or each message is shown twice.
- A member is matched against the form model's own properties. A violation with a dotted member (
Address.City) or one about a collection (Items) still appears in<ValidationSummary>, but<ValidationMessage For="() => Model.Address.City">will not show it, because Blazor keys that field by theAddressobject. - Only point-in-time rules run (
ConstraintSet<T>.Validate); sequential rules (Transitions,Monotonic) need a previous value, which a form does not have. - Constraints are synchronous. For a check that needs a server (is this email taken), validate in the submit handler and add the message yourself.
The package depends on Nullean.Entities and Microsoft.AspNetCore.Components.Forms (10.0 or later), and works with any render mode, including WebAssembly. It uses no reflection and builds with
the trim and AOT analyzers on; it is not yet published in this repository's NativeAOT smoke test or a trimmed WebAssembly app.
Every snippet on this page is backed by tests/Nullean.Entities.Blazor.Tests (SignUpForm.razor, ContactForm.razor, ConstraintValidatorTests.cs), which renders the forms with bUnit on net10.0 and net11.0.