# Blazor Forms & Validation
## EditForm Component
EditForm provides a complete form handling solution with data binding and validation.
### Basic EditForm
```csharp
@page "/register"
@code {
private RegistrationModel model = new();
private async Task HandleValidSubmit()
{
// Form is valid, process data
await Service.RegisterUserAsync(model);
}
}
public class RegistrationModel
{
[Required]
[StringLength(100, MinimumLength = 2)]
public string Name { get; set; } = "";
[Required]
[EmailAddress]
public string Email { get; set; } = "";
[Required]
[MinLength(8)]
public string Password { get; set; } = "";
}
```
### EditForm Events
```csharp
@code {
private async Task OnValidSubmit()
{
// Fires when form is valid and submitted
}
private async Task OnInvalidSubmit()
{
// Fires when form is invalid and submitted
}
private async Task OnSubmit()
{
// Fires for any submit (valid or invalid)
// Useful for custom validation logic
}
}
```
### Form State Control
```csharp
@inject EditFormService FormService
@code {
private EditForm? form;
private UserModel model = new();
private async Task Submit()
{
// Manually trigger validation and submission
await form!.RequestValidationAsync();
// Check if valid
if (form!.EditContext.IsModified() && form!.EditContext.Validate())
{
// Process form
}
}
private void Reset()
{
// Reset all fields to default
form!.EditContext.ResetEditingItemAsync();
}
private void CheckValid()
{
bool isValid = form!.EditContext.Validate();
Console.WriteLine($"Form valid: {isValid}");
}
}
```
## Input Components
### Text Input
```csharp
@code {
private UserModel model = new();
}
```
### Numeric Input
```csharp
@code {
private int age;
private decimal price;
}
```
**Format specifiers:**
- `N2` - Number with 2 decimal places
- `C` - Currency
- `P` - Percentage
- `D` - Date
- `X` - Hexadecimal
### Date Input
```csharp
@code {
private DateTime birthDate;
private DateTime startTime;
}
```
**Types:**
- `InputDateType.Date` - Date only (default)
- `InputDateType.DateTimeLocal` - Date and time
- `InputDateType.Month` - Month and year
- `InputDateType.Time` - Time only
### Select/Dropdown
```csharp
@foreach (var cat in categories)
{
}
@code {
private string selectedCategory = "";
private List categories = [];
}
```
### Checkbox
```csharp
Accept terms of service?
@code {
private bool agreeToTerms = false;
}
```
### Radio Buttons
```csharp
@code {
private string preference = "option1";
}
```
### File Upload
```csharp
@code {
private async Task HandleFileSelect(InputFileChangeEventArgs e)
{
var file = e.File;
using var stream = file.OpenReadStream();
var buffer = new byte[stream.Length];
await stream.ReadAsync(buffer);
// Process file
}
}
```
## Validation
### DataAnnotations Validation
```csharp
public class UserModel
{
[Required(ErrorMessage = "Name is required")]
[StringLength(100, MinimumLength = 2)]
public string Name { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; } = "";
[Range(18, 120, ErrorMessage = "Age must be 18-120")]
public int Age { get; set; }
[Url]
public string? Website { get; set; }
[Phone]
public string? PhoneNumber { get; set; }
[CreditCard]
public string? CardNumber { get; set; }
}
```
**Common Validators:**
- `[Required]` - Field must have value
- `[StringLength(max)]` - Max length
- `[StringLength(max, MinimumLength = min)]` - Min and max
- `[EmailAddress]` - Valid email format
- `[Range(min, max)]` - Numeric range
- `[Url]` - Valid URL format
- `[Phone]` - Valid phone format
- `[CreditCard]` - Valid credit card format
- `[RegularExpression(pattern)]` - Regex match
### ValidationSummary
Shows all validation errors for the form:
```csharp
```
Displays as:
```
- Name is required
- Email is required
```
### ValidationMessage
Shows validation error for specific field:
```csharp
```
### Custom Validation
Implement `IValidatableObject` for complex validation rules:
```csharp
public class UserModel : IValidatableObject
{
public string Email { get; set; } = "";
public string ConfirmEmail { get; set; } = "";
[Range(18, 100)]
public int Age { get; set; }
public IEnumerable Validate(ValidationContext validationContext)
{
// Compare email fields
if (Email != ConfirmEmail)
{
yield return new ValidationResult(
"Email addresses must match",
new[] { nameof(ConfirmEmail) }
);
}
// Custom age validation
if (Age > 0 && Age < 18 && HasRestrictedContent)
{
yield return new ValidationResult(
"Users under 18 cannot access this content",
new[] { nameof(Age) }
);
}
}
public bool HasRestrictedContent { get; set; }
}
```
### Custom Validators
Create reusable custom validators:
```csharp
public class MinimumAgeAttribute : ValidationAttribute
{
private readonly int _minimumAge;
public MinimumAgeAttribute(int minimumAge)
{
_minimumAge = minimumAge;
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value is DateTime birthDate)
{
var age = DateTime.Today.Year - birthDate.Year;
if (birthDate.Date > DateTime.Today.AddYears(-age)) age--;
if (age < _minimumAge)
{
return new ValidationResult($"Minimum age is {_minimumAge}");
}
}
return ValidationResult.Success;
}
}
// Usage
public class UserModel
{
[MinimumAge(18)]
public DateTime BirthDate { get; set; }
}
```
### Async Validation
```csharp
public class UniqueEmailAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
// Can't use async in ValidationAttribute
// Use EditContext instead (see below)
return ValidationResult.Success;
}
}
// Better approach: Manual validation in component
@code {
private async Task HandleValidSubmit()
{
// Check email availability before submit
bool isUnique = await Service.IsEmailUniqueAsync(model.Email);
if (!isUnique)
{
form!.EditContext.AddValidationMessages(
FieldIdentifier.Create(() => model.Email),
new[] { "Email is already registered" }
);
return;
}
await SaveUserAsync(model);
}
}
```
## Form Patterns
### Loading State
```csharp
@if (isSubmitting)
{