From a0b074c9e0d063f5050c571ef2f1b5f705cd23d7 Mon Sep 17 00:00:00 2001 From: TheXamlGuy Date: Sun, 16 Jun 2024 13:42:36 +0100 Subject: [PATCH] Added Validation logics --- Toolkit.Avalonia/ContentDialogHandler.cs | 2 - Toolkit.Foundation/ComponentBuilder.cs | 3 + Toolkit.Foundation/DefaultHostBuilder.cs | 8 +- Toolkit.Foundation/IValidation.cs | 20 ++++ Toolkit.Foundation/Validate.cs | 10 ++ Toolkit.Foundation/ValidateEventArgs.cs | 3 + Toolkit.Foundation/Validation.cs | 93 +++++++++++++++++-- .../ValidationErrorCollection.cs | 18 ++-- Toolkit.Foundation/ValidationEventArgs.cs | 3 - ...PropertyValidator.cs => ValidationRule.cs} | 13 ++- Toolkit.Foundation/ValidationTrigger.cs | 7 ++ Toolkit.Foundation/Validator.cs | 38 ++------ 12 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 Toolkit.Foundation/IValidation.cs create mode 100644 Toolkit.Foundation/Validate.cs create mode 100644 Toolkit.Foundation/ValidateEventArgs.cs delete mode 100644 Toolkit.Foundation/ValidationEventArgs.cs rename Toolkit.Foundation/{PropertyValidator.cs => ValidationRule.cs} (53%) create mode 100644 Toolkit.Foundation/ValidationTrigger.cs diff --git a/Toolkit.Avalonia/ContentDialogHandler.cs b/Toolkit.Avalonia/ContentDialogHandler.cs index 5c6a1a6..c1aeef8 100644 --- a/Toolkit.Avalonia/ContentDialogHandler.cs +++ b/Toolkit.Avalonia/ContentDialogHandler.cs @@ -94,8 +94,6 @@ public class ContentDialogHandler(IDispatcher dispatcher) : deactivatable.DeactivateHandler += DeactivateHandler; } - // A hack to wait for the dialog to finish loading up to make it appear more responsive - await Task.Delay(250); if (content is IActivated activated) { await activated.OnActivated(); diff --git a/Toolkit.Foundation/ComponentBuilder.cs b/Toolkit.Foundation/ComponentBuilder.cs index 53a6a9b..ca508c4 100644 --- a/Toolkit.Foundation/ComponentBuilder.cs +++ b/Toolkit.Foundation/ComponentBuilder.cs @@ -36,6 +36,9 @@ public class ComponentBuilder : services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); diff --git a/Toolkit.Foundation/DefaultHostBuilder.cs b/Toolkit.Foundation/DefaultHostBuilder.cs index 5d7a70c..b58e9db 100644 --- a/Toolkit.Foundation/DefaultHostBuilder.cs +++ b/Toolkit.Foundation/DefaultHostBuilder.cs @@ -29,13 +29,13 @@ public class DefaultHostBuilder : services.AddScoped(); services.AddTransient(); - services.AddTransient(); - - services.AddTransient(); - + services.AddScoped(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddScoped>(provider => new ProxyService(provider.GetRequiredService())); diff --git a/Toolkit.Foundation/IValidation.cs b/Toolkit.Foundation/IValidation.cs new file mode 100644 index 0000000..891f08f --- /dev/null +++ b/Toolkit.Foundation/IValidation.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using System.Linq.Expressions; + +namespace Toolkit.Foundation; + +public interface IValidation : + INotifyPropertyChanged +{ + ValidationErrorCollection Errors { get; } + + bool HasErrors { get; } + + void Add(Expression> property, + ValidationRule[] rules, + ValidationTrigger trigger = ValidationTrigger.Deferred); + + bool Validate(); + + bool Validate(string name); +} \ No newline at end of file diff --git a/Toolkit.Foundation/Validate.cs b/Toolkit.Foundation/Validate.cs new file mode 100644 index 0000000..1a1e9d1 --- /dev/null +++ b/Toolkit.Foundation/Validate.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Validate +{ + public static ValidateEventArgs As(TValue value) => + new(value); + + public static ValidateEventArgs As() where TValue : new() => + new(new TValue()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/ValidateEventArgs.cs b/Toolkit.Foundation/ValidateEventArgs.cs new file mode 100644 index 0000000..ec1b6e1 --- /dev/null +++ b/Toolkit.Foundation/ValidateEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record ValidateEventArgs(TValue Value); \ No newline at end of file diff --git a/Toolkit.Foundation/Validation.cs b/Toolkit.Foundation/Validation.cs index d3c35ed..82e9a8c 100644 --- a/Toolkit.Foundation/Validation.cs +++ b/Toolkit.Foundation/Validation.cs @@ -1,10 +1,89 @@ -namespace Toolkit.Foundation; +using System.ComponentModel; +using System.Linq.Expressions; -public record Validation +namespace Toolkit.Foundation; + +public class Validation(IValidatorCollection validators) : + IValidation { - public static ValidationEventArgs As(TValue value) => - new(value); + public event PropertyChangedEventHandler? PropertyChanged; - public static ValidationEventArgs As() where TValue : new() => - new(new TValue()); -} \ No newline at end of file + public ValidationErrorCollection Errors { get; } = []; + + public bool HasErrors => + Errors.Count > 0; + + internal IValidatorCollection Validators { get; } = validators; + + public void Add(Expression> property, + ValidationRule[] rules, + ValidationTrigger trigger = ValidationTrigger.Deferred) + { + string? name = GetPropertyName(property); + Validators.Add(name, new Validator(name, rules)); + + if (trigger is ValidationTrigger.Immediate) + { + Validate(name); + } + } + + public virtual void OnPropertyChanged(string propertyName, + object? before, object? after) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public bool Validate(string name) + { + if (Errors.ContainsKey(name)) + { + Errors.Remove(name); + } + + if (Validators.TryGet(name, out Validator? validator)) + { + if (validator is not null) + { + if (!validator.TryValidate(out string? message)) + { + Errors[name] = message ?? ""; + } + } + } + + OnPropertyChanged(nameof(Errors), null, null); + OnPropertyChanged(nameof(HasErrors), null, null); + + return !HasErrors; + } + + public bool Validate() + { + Errors.Clear(); + foreach (Validator? validator in Validators) + { + if (validator.PropertyName is string name) + { + if (!validator.TryValidate(out string? message)) + { + Errors[name] = message ?? ""; + } + } + } + + OnPropertyChanged(nameof(Errors), null, null); + OnPropertyChanged(nameof(HasErrors), null, null); + + return !HasErrors; + } + + private string GetPropertyName(Expression> predicate) + { + Expression? body = predicate.Body; + MemberExpression? memberExpression = body as MemberExpression ?? + (MemberExpression)((UnaryExpression)body).Operand; + + return memberExpression.Member.Name; + } +} diff --git a/Toolkit.Foundation/ValidationErrorCollection.cs b/Toolkit.Foundation/ValidationErrorCollection.cs index 31f4d7b..1c360db 100644 --- a/Toolkit.Foundation/ValidationErrorCollection.cs +++ b/Toolkit.Foundation/ValidationErrorCollection.cs @@ -5,10 +5,11 @@ using System.Diagnostics.CodeAnalysis; namespace Toolkit.Foundation; -public class ValidationErrorCollection : IDictionary, - IDictionary, - INotifyCollectionChanged, - INotifyPropertyChanged +public class ValidationErrorCollection : + IDictionary, + IDictionary, + INotifyCollectionChanged, + INotifyPropertyChanged { private Dictionary items; @@ -41,11 +42,7 @@ public class ValidationErrorCollection : IDictionary, public string this[string key] { - get - { - return items[key]; - } - + get => items.ContainsKey(key) ? items[key] : ""; set { bool replace = items.TryGetValue(key, out var old); @@ -54,7 +51,8 @@ public class ValidationErrorCollection : IDictionary, if (replace) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, new KeyValuePair(key, value), new KeyValuePair(key, old!))); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, + new KeyValuePair(key, value), new KeyValuePair(key, old!))); } else { diff --git a/Toolkit.Foundation/ValidationEventArgs.cs b/Toolkit.Foundation/ValidationEventArgs.cs deleted file mode 100644 index 2e19125..0000000 --- a/Toolkit.Foundation/ValidationEventArgs.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Toolkit.Foundation; - -public record ValidationEventArgs(TValue Value); \ No newline at end of file diff --git a/Toolkit.Foundation/PropertyValidator.cs b/Toolkit.Foundation/ValidationRule.cs similarity index 53% rename from Toolkit.Foundation/PropertyValidator.cs rename to Toolkit.Foundation/ValidationRule.cs index fac8d17..d420905 100644 --- a/Toolkit.Foundation/PropertyValidator.cs +++ b/Toolkit.Foundation/ValidationRule.cs @@ -1,22 +1,27 @@ namespace Toolkit.Foundation; -public class PropertyValidator +public class ValidationRule { - public PropertyValidator(Func validation, + public ValidationRule(Func validation, string message) { Validation = validation; Message = new Func(() => message); } - public PropertyValidator(Func validation, + public ValidationRule(Func validation) + { + Validation = validation; + } + + public ValidationRule(Func validation, Func message) { Validation = validation; Message = message; } - public Func Message { get; } + public Func? Message { get; } public Func? Validation { get; } } diff --git a/Toolkit.Foundation/ValidationTrigger.cs b/Toolkit.Foundation/ValidationTrigger.cs new file mode 100644 index 0000000..d969d8e --- /dev/null +++ b/Toolkit.Foundation/ValidationTrigger.cs @@ -0,0 +1,7 @@ +namespace Toolkit.Foundation; + +public enum ValidationTrigger +{ + Deferred, + Immediate +} diff --git a/Toolkit.Foundation/Validator.cs b/Toolkit.Foundation/Validator.cs index 5e8257a..e1e59f1 100644 --- a/Toolkit.Foundation/Validator.cs +++ b/Toolkit.Foundation/Validator.cs @@ -4,48 +4,30 @@ namespace Toolkit.Foundation; public class Validator { - private readonly Action? propertyChanged; - private readonly PropertyValidator? propertyValidation; + private readonly ValidationRule[] rules = []; - internal Validator(string propertyName, - Action propertyChanged) + public Validator(string propertyName, + ValidationRule[] rules) { PropertyName = propertyName; - this.propertyChanged = propertyChanged; - } - - internal Validator(string propertyName, - Action propertyChanged, - PropertyValidator validation) - { - PropertyName = propertyName; - - this.propertyChanged = propertyChanged; - propertyValidation = validation; - } - - internal Validator(string propertyName, - PropertyValidator validation) - { - PropertyName = propertyName; - propertyValidation = validation; + this.rules = rules; } public string? PropertyName { get; } - public void Set() => propertyChanged?.Invoke(); - public bool TryValidate([MaybeNull] out string message) { message = ""; - if (propertyValidation is not null && propertyValidation.Validation?.Invoke() == false) + foreach (ValidationRule rule in rules) { - message = propertyValidation.Message.Invoke(); - return false; + if (rule.Validation?.Invoke() == false) + { + message = rule.Message?.Invoke(); + return false; + } } - propertyChanged?.Invoke(); return true; } }