using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Components.Forms; using Microsoft.EntityFrameworkCore; using MudBlazor; namespace WebApp.Services; /// /// Service for handling form validation errors and displaying them to users /// public class FormValidationService { private readonly ISnackbar _snackbar; public FormValidationService(ISnackbar snackbar) { _snackbar = snackbar; } /// /// Collects all validation errors from an EditContext and model validation /// /// The EditContext from the form /// The model being validated /// List of validation error messages public List CollectValidationErrors(EditContext editContext, object model) { var errors = new List(); // Validate the entire model using data annotations var validationResults = new List(); var validationContext = new ValidationContext(model); bool isValid = Validator.TryValidateObject(model, validationContext, validationResults, true); if (!isValid) { foreach (var result in validationResults) { foreach (var memberName in result.MemberNames) { var errorMessage = $"{memberName}: {result.ErrorMessage}"; if (!errors.Contains(errorMessage)) { errors.Add(errorMessage); } } } } // Also get validation messages from EditContext (this gets all field-level messages) var validationMessages = editContext.GetValidationMessages(); foreach (var message in validationMessages) { if (!errors.Contains(message)) { errors.Add(message); } } return errors; } /// /// Handles invalid form submission by collecting errors and displaying them /// /// The EditContext from the form /// The model being validated /// Optional callback to handle the collected errors (e.g., store in component state) /// List of validation error messages public List HandleInvalidSubmit(EditContext editContext, object model, Action>? onErrorsCollected = null) { var errors = CollectValidationErrors(editContext, model); if (onErrorsCollected != null) { onErrorsCollected(errors); } if (errors.Any()) { var errorText = string.Join("; ", errors); _snackbar.Add($"Validation failed: {errorText}", Severity.Error); } else { _snackbar.Add("Please correct the validation errors and try again.", Severity.Warning); } return errors; } /// /// Handles DbUpdateException and displays user-friendly error messages /// /// The DbUpdateException to handle /// Default error message if specific handling isn't available /// Name of the entity being saved (e.g., "event", "student", "team") /// Operation being performed (e.g., "saving", "creating") /// Field name for UNIQUE constraint errors (e.g., "name", "email") public void HandleDbUpdateException( DbUpdateException ex, string defaultErrorMessage, string entityName = "record", string operation = "saving", string? uniqueConstraintField = null) { var errorMessage = defaultErrorMessage; if (ex.InnerException != null) { var innerMessage = ex.InnerException.Message; if (innerMessage.Contains("UNIQUE constraint", StringComparison.OrdinalIgnoreCase) || innerMessage.Contains("duplicate key", StringComparison.OrdinalIgnoreCase)) { if (!string.IsNullOrEmpty(uniqueConstraintField)) { errorMessage = $"A {entityName} with this {uniqueConstraintField} already exists. Please choose a different {uniqueConstraintField}."; } else { errorMessage = $"A {entityName} with this information already exists. Please check for duplicates."; } } else if (innerMessage.Contains("FOREIGN KEY constraint", StringComparison.OrdinalIgnoreCase)) { errorMessage = $"Cannot {operation}: This {entityName} is referenced by other records."; } else { errorMessage = $"Database error: {innerMessage}"; } } _snackbar.Add(errorMessage, Severity.Error); } /// /// Handles generic exceptions and displays user-friendly error messages /// /// The Exception to handle public void HandleException(Exception ex) { var errorMessage = $"An unexpected error occurred: {ex.Message}"; _snackbar.Add(errorMessage, Severity.Error); } /// /// Handles DbUpdateConcurrencyException and displays user-friendly error messages /// /// The DbUpdateConcurrencyException to handle /// Function to check if the entity still exists /// Name of the entity (e.g., "event", "student", "team") /// Optional action to perform when entity is not found (e.g., navigate to notfound page) /// True if the exception was handled (entity not found), false if it should be rethrown public bool HandleDbUpdateConcurrencyException( DbUpdateConcurrencyException ex, Func entityExists, string entityName = "record", Action? onNotFound = null) { if (!entityExists()) { _snackbar.Add($"{entityName.Substring(0, 1).ToUpper()}{entityName.Substring(1)} not found. It may have been deleted.", Severity.Error); onNotFound?.Invoke(); return true; } else { _snackbar.Add($"The {entityName} was modified by another user. Please refresh and try again.", Severity.Warning); return false; } } }