# 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) {

Saving...

} else { } @code { private bool isSubmitting; private async Task SubmitAsync() { isSubmitting = true; try { await Service.SaveAsync(model); } finally { isSubmitting = false; } } } ``` ### Error Handling ```csharp @if (!string.IsNullOrEmpty(errorMessage)) {
@errorMessage
}
@code { private string? errorMessage; private async Task SubmitAsync() { try { errorMessage = null; await Service.SaveAsync(model); } catch (Exception ex) { errorMessage = $"Error: {ex.Message}"; } } } ``` ### Multi-Step Form ```csharp @page "/wizard" @if (currentStep == 1) {

Step 1: Basic Info

} else if (currentStep == 2) {

Step 2: Contact Info

} else if (currentStep == 3) {

Step 3: Confirm

Name: @model.Name

Email: @model.Email

} @code { private int currentStep = 1; private UserModel model = new(); private void NextStep() => currentStep++; private void PreviousStep() => currentStep--; private async Task SubmitAsync() { await Service.RegisterAsync(model); } } ``` ### Real-Time Field Validation ```csharp @if (!string.IsNullOrEmpty(emailError)) { @emailError } @code { private string email = ""; private string? emailError; private void ValidateEmail() { if (string.IsNullOrEmpty(email)) { emailError = "Email is required"; } else if (!email.Contains("@")) { emailError = "Invalid email format"; } else { emailError = null; } } } ``` --- **Related Resources:** See [state-management-events.md](state-management-events.md) for data binding patterns. See [authentication-authorization.md](authentication-authorization.md) for role-based form customization.