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;
}
}
}