Forms & Validation
Key Points
<EditForm>with aModel. Built-in: DataAnnotations validator, validation summary, validation message.InputText,InputNumber,InputSelect,InputCheckbox,InputDate,InputRadioGroup,InputTextArea— typed inputs with two-way bind + validation.- FluentValidation integration via Blazored.FluentValidation package — most senior teams prefer FluentValidation.
- Server-side validation always — client-side is UX, not security.
- Static SSR forms post via HTTP; Interactive forms validate live.
Concepts (deep dive)
Basic EditForm
<EditForm Model="@_user" OnValidSubmit="@HandleValid">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText @bind-Value="_user.Name" />
<ValidationMessage For="@(() => _user.Name)" />
<InputNumber @bind-Value="_user.Age" />
<ValidationMessage For="@(() => _user.Age)" />
<button type="submit">Save</button>
</EditForm>
@code {
User _user = new();
void HandleValid()
{
// _user is valid; save
}
}
public class User
{
[Required, StringLength(100)]
public string? Name { get; set; }
[Range(0, 150)]
public int Age { get; set; }
}
EditForm events
<EditForm Model="@_m"
OnSubmit="@OnAnySubmit"
OnValidSubmit="@OnValid"
OnInvalidSubmit="@OnInvalid" />
Built-in inputs
| Component | For type |
|---|---|
InputText | string |
InputNumber<T> | int, decimal, double |
InputSelect<T> | enum / item |
InputCheckbox | bool |
InputDate<T> | DateTime / DateOnly |
InputRadioGroup<T> + InputRadio<T> | enum |
InputTextArea | multi-line |
InputFile | file upload |
FluentValidation integration
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(u => u.Name).NotEmpty().MaximumLength(100);
RuleFor(u => u.Age).InclusiveBetween(0, 150);
RuleFor(u => u.Email).EmailAddress().When(u => !string.IsNullOrEmpty(u.Email));
}
}
builder.Services.AddValidatorsFromAssemblyContaining<UserValidator>();
<EditForm Model="@_user" OnValidSubmit="@Save">
<FluentValidationValidator />
<ValidationSummary />
<!-- ... -->
</EditForm>
Same validator for client (live) and server (form post). Single source of truth.
Custom validation
public class FutureDateAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext ctx)
{
if (value is DateTime d && d < DateTime.UtcNow)
return new ValidationResult("Must be in the future", new[] { ctx.MemberName! });
return ValidationResult.Success;
}
}
Server validation in Static SSR
@page "/register"
@inject IUserService Users
<EditForm Model="@Input" method="post" OnValidSubmit="@RegisterAsync" FormName="Register">
<DataAnnotationsValidator />
<InputText @bind-Value="Input.Email" />
<ValidationMessage For="@(() => Input.Email)" />
<button>Register</button>
</EditForm>
@code {
[SupplyParameterFromForm]
public RegisterModel Input { get; set; } = new();
async Task RegisterAsync()
{
if (await Users.EmailExists(Input.Email))
{
// Re-render with error
return;
}
// ...
}
}
[SupplyParameterFromForm] binds POST data. Static SSR forms work like classic POST.
File upload
<InputFile OnChange="@OnFileSelected" multiple />
@code {
async Task OnFileSelected(InputFileChangeEventArgs e)
{
foreach (var file in e.GetMultipleFiles())
{
using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
// process
}
}
}
EditContext
For lower-level control:
private EditContext _ctx = null!;
protected override void OnInitialized()
{
_ctx = new EditContext(_model);
_ctx.OnFieldChanged += (s, e) => Console.WriteLine($"Changed {e.FieldIdentifier}");
}
Server-side anti-forgery
For Static SSR forms, <EditForm> auto-includes anti-forgery token via <AntiforgeryToken>.
Validation messages styling
CSS classes: valid, modified, invalid. Bootstrap integration:
Customize via FieldCssClassProvider.
Real-time validation
Default: validate on field blur. For keystroke validation:
Validation in Interactive Server vs WASM
- Server: validation runs on server (over SignalR).
- WASM: validation runs in browser (no server roundtrip).
- Static SSR: validation runs on form post (server).
Cross-field validation
public class AppointmentValidator : AbstractValidator<Appointment>
{
public AppointmentValidator()
{
RuleFor(a => a).Must(a => a.End > a.Start).WithMessage("End must be after start");
}
}
Or DataAnnotations IValidatableObject.
Code: correct vs wrong
❌ Wrong: client-only validation
✅ Correct: validate on server too
[HttpPost]
public IActionResult Save(User u)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
/* save */
}
❌ Wrong: file upload without size limit
✅ Correct: explicit limit
Design patterns for this topic
Pattern 1 — "FluentValidation single-source"
- Intent: same rules client + server.
Pattern 2 — "EditContext for advanced"
- Intent: per-field events, dynamic.
Pattern 3 — "Server-validate even with client-validate"
- Intent: UX + security.
Pattern 4 — "Static SSR forms with [SupplyParameterFromForm]"
- Intent: classic POST workflow.
Pattern 5 — "Real-time validation via oninput"
- Intent: keystroke feedback.
Pros & cons / trade-offs
| Validator | Pros | Cons |
|---|---|---|
| DataAnnotations | Built-in | Limited |
| FluentValidation | Powerful; fluent | Extra dep |
| Custom IValidatableObject | Flexible | Boilerplate |
When to use / when to avoid
- Use FluentValidation in mid+ projects.
- Use server validation always.
- Use Static SSR forms for simple submit flows.
- Avoid trusting client validation alone.
Interview Q&A
Q1. EditForm purpose? Provides EditContext, validation, event hooks for forms.
Q2. DataAnnotations vs FluentValidation? DA: built-in attributes. FV: code-based; richer.
Q3. ValidationSummary vs ValidationMessage? Summary: all errors. Message: per-field.
Q4. Server validation needed? Always. Client is UX; server is security.
Q5. [SupplyParameterFromForm]? Binds form POST data to component property. Static SSR.
Q6. Cross-field validation? FluentValidation RuleFor(m => m).Must(...) or IValidatableObject.
Q7. Real-time validation? @bind-Value:event="oninput" updates on every keystroke.
Q8. EditContext use case? OnFieldChanged events; manual validation; advanced control.
Q9. File upload size limit? Pass maxAllowedSize. Default 500 KB.
Q10. Anti-forgery in Blazor forms? Auto with EditForm in Static SSR. AntiforgeryToken if hand-rolled.
Q11. InputDate vs DateTime input? InputDate is the typed Blazor wrapper. Auto bind + validation.
Q12. Validation differs by render mode? Logic is the same; runs on server (Server/Static) or client (WASM).
Gotchas / common mistakes
- ⚠️ No server validation.
- ⚠️ File size limits default-low.
- ⚠️ DataAnnotations only for complex rules.
- ⚠️ Forgot anti-forgery in Static SSR custom forms.
- ⚠️ EditForm without Model OR EditContext.