CustomFluentValidationManager.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. using BlazorEditForms.Model;
  2. using FluentValidation;
  3. using FluentValidation.Internal;
  4. using FluentValidation.Validators;
  5. using Microsoft.AspNetCore.Components.Forms;
  6. namespace BlazorEditForms;
  7. public class CustomFluentValidationManager : IDisposable
  8. {
  9. private readonly EventHandler<ValidationRequestedEventArgs> _validationRequestedHandler;
  10. private readonly EventHandler<FieldChangedEventArgs> _fieldChangedHandler;
  11. private readonly EditContext _editContext;
  12. private readonly IServiceProvider _serviceProvider;
  13. private readonly IValidator<Aggregate> _validator;
  14. private readonly ValidationMessageStore _validationMessageStore;
  15. public CustomFluentValidationManager(EditContext editContext, IServiceProvider serviceProvider)
  16. {
  17. _editContext = editContext;
  18. _serviceProvider = serviceProvider;
  19. _validator = new AggregateValidator();
  20. _validationRequestedHandler = async (sender, eventArgs) => await ValidateModel();
  21. _editContext.OnValidationRequested += _validationRequestedHandler;
  22. _fieldChangedHandler = async (sender, eventArgs) => await ValidateField(eventArgs.FieldIdentifier);
  23. _editContext.OnFieldChanged += _fieldChangedHandler;
  24. // This validation message store is _internally_ connected to the edit context and essentially serves as a
  25. // separate API to manage the validation messages that can (with public methods) only be _extracted from_ the
  26. // edit context.
  27. _validationMessageStore = new ValidationMessageStore(_editContext);
  28. // TODO: how to make this multi-threaded ?
  29. ValidatorOptions.Global.OnFailureCreated = (failure, context, value, rule, component) =>
  30. {
  31. if (!string.IsNullOrEmpty(rule.PropertyName))
  32. {
  33. failure.FormattedMessagePlaceholderValues["MINE"] =
  34. new FieldIdentifier(context.InstanceToValidate, rule.PropertyName);
  35. }
  36. return failure;
  37. };
  38. }
  39. private async Task ValidateModel()
  40. {
  41. var validationContext = ValidationContext<object>.CreateWithOptions(_editContext.Model, strategy =>
  42. {
  43. //strategy.UseCustomSelector(new MySelector());
  44. strategy.IncludeRulesNotInRuleSet();
  45. });
  46. var validationResult = await _validator.ValidateAsync(validationContext);
  47. _validationMessageStore.Clear();
  48. foreach (var failure in validationResult.Errors)
  49. {
  50. if (failure.FormattedMessagePlaceholderValues.TryGetValue("MINE", out var mine))
  51. {
  52. var fieldIdentifier = (FieldIdentifier)mine;
  53. _validationMessageStore.Add(fieldIdentifier, failure.ErrorMessage);
  54. }
  55. }
  56. _editContext.NotifyValidationStateChanged();
  57. }
  58. private async Task ValidateField(FieldIdentifier fieldIdentifier)
  59. {
  60. var validationContext = ValidationContext<object>.CreateWithOptions(_editContext.Model, strategy =>
  61. {
  62. strategy.UseCustomSelector(new FieldIdentifierSelector(fieldIdentifier));
  63. //strategy.IncludeRulesNotInRuleSet();
  64. });
  65. var validationResult = await _validator.ValidateAsync(validationContext);
  66. _validationMessageStore.Clear(fieldIdentifier);
  67. foreach (var failure in validationResult.Errors)
  68. {
  69. if (failure.FormattedMessagePlaceholderValues.TryGetValue("MINE", out var mine))
  70. {
  71. if (fieldIdentifier.Equals(mine))
  72. {
  73. _validationMessageStore.Add(fieldIdentifier, failure.ErrorMessage);
  74. }
  75. }
  76. }
  77. _editContext.NotifyValidationStateChanged();
  78. }
  79. private class FieldIdentifierSelector : IValidatorSelector
  80. {
  81. private readonly FieldIdentifier _fieldIdentifier;
  82. public FieldIdentifierSelector(FieldIdentifier fieldIdentifier)
  83. {
  84. _fieldIdentifier = fieldIdentifier;
  85. }
  86. public bool CanExecute(IValidationRule rule, string propertyPath, IValidationContext context)
  87. {
  88. return rule.Components.Any(c => c.Validator is IChildValidatorAdaptor) || (context.InstanceToValidate == _fieldIdentifier.Model &&
  89. rule.PropertyName == _fieldIdentifier.FieldName);
  90. }
  91. }
  92. public void Dispose()
  93. {
  94. _editContext.OnValidationRequested -= _validationRequestedHandler;
  95. _editContext.OnFieldChanged -= _fieldChangedHandler;
  96. }
  97. }