diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln b/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln index d009e7a..8dbb4eb 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValidationRulesTest", "Vali EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ValidationRules", "..\..\..\src\ValidationRules\Plugin.ValidationRules.csproj", "{74CB3C79-59A7-4AAA-A94F-EDA9D278847E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Reactive.ValidationRules", "..\..\..\src\Plugin.Reactive.ValidationRules\Plugin.Reactive.ValidationRules.csproj", "{44FC6F88-FC72-4DC9-B6EB-E76B896A3861}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,6 +25,10 @@ Global {74CB3C79-59A7-4AAA-A94F-EDA9D278847E}.Debug|Any CPU.Build.0 = Debug|Any CPU {74CB3C79-59A7-4AAA-A94F-EDA9D278847E}.Release|Any CPU.ActiveCfg = Release|Any CPU {74CB3C79-59A7-4AAA-A94F-EDA9D278847E}.Release|Any CPU.Build.0 = Release|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44FC6F88-FC72-4DC9-B6EB-E76B896A3861}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj index 17c6f3f..f6d5791 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj @@ -50,6 +50,7 @@ + diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs new file mode 100644 index 0000000..93b72a9 --- /dev/null +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Plugin.Reactive.ValidationRules; +using Plugin.ValidationRules.Extensions; +using Plugin.ValidationRules.Interfaces; +using Plugin.ValidationRules.Rules; +using ValidationRulesTest.Validations; +using EmailRule = Plugin.ValidationRules.Rules.EmailRule; + +namespace ValidationRulesTest.ViewModels +{ + public class ReactiveValidationExample1ViewModel : ExtendedPropertyChanged, IDisposable + { + ReactiveValidatable _name; + public ReactiveValidatable Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + public ReactiveValidatable _email; + public ReactiveValidatable Email + { + get => _email; + set => SetProperty(ref _email, value); + } + + public ReactiveValidatable _password; + public ReactiveValidatable Password + { + get => _password; + set => SetProperty(ref _password, value); + } + + public ReactiveValidationExample1ViewModel() + { + Name = new ReactiveValidatable( + string.Empty, new NotEmptyRule(string.Empty) + { + ValidationMessage = "Name is required" + }); + + Email = new ReactiveValidatable(string.Empty, + new EmailRule() + { + ValidationMessage = "Email wrongly formatted" + }, + new NotEmptyRule(string.Empty) + { + ValidationMessage = "Email is required" + }); + + Password = new ReactiveValidatable(string.Empty, new PasswordRule(), new NotEmptyRule(string.Empty) + { + ValidationMessage = "Password is required" + }); + } + + public void Dispose() + { + _name?.Dispose(); + _email?.Dispose(); + _password?.Dispose(); + } + } +} diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml index f97d39b..b80b9a2 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml @@ -52,6 +52,12 @@ Clicked="example8_Clicked" Text="Example 8" /> + + diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs index 5e5842c..a6347f8 100644 --- a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs @@ -47,5 +47,9 @@ private void example8_Clicked(object sender, EventArgs e) { Navigation.PushAsync(new Example8()); } + private void ReactiveValidationExample1_Clicked(object sender, EventArgs e) + { + Navigation.PushAsync(new ReactiveValidationExample1()); + } } } \ No newline at end of file diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml new file mode 100644 index 0000000..3f034c7 --- /dev/null +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs new file mode 100644 index 0000000..4dc378b --- /dev/null +++ b/samples/Maui/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs @@ -0,0 +1,23 @@ +using ValidationRulesTest.ViewModels; + +namespace ValidationRulesTest.Views +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class ReactiveValidationExample1 : ContentPage + { + ReactiveValidationExample1ViewModel _viewModel; + + public ReactiveValidationExample1() + { + InitializeComponent(); + _viewModel = new ReactiveValidationExample1ViewModel(); + BindingContext = _viewModel; + } + + protected override void OnDisappearing() + { + _viewModel.Dispose(); + base.OnDisappearing(); + } + } +} \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest.sln b/samples/Xamarin.Forms/ValidationRulesTest.sln index b008074..9eceb37 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest.sln +++ b/samples/Xamarin.Forms/ValidationRulesTest.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValidationRulesTest", "Vali EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ValidationRules", "..\..\src\ValidationRules\Plugin.ValidationRules.csproj", "{1DA953F3-E1A6-425A-B823-629006A45561}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.Reactive.ValidationRules", "..\..\src\Plugin.Reactive.ValidationRules\Plugin.Reactive.ValidationRules.csproj", "{AAC08845-4F44-4851-B512-372380949518}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -159,6 +161,30 @@ Global {1DA953F3-E1A6-425A-B823-629006A45561}.Release|iPhone.Build.0 = Release|Any CPU {1DA953F3-E1A6-425A-B823-629006A45561}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {1DA953F3-E1A6-425A-B823-629006A45561}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhone.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhone.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|Any CPU.Build.0 = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhone.ActiveCfg = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhone.Build.0 = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {AAC08845-4F44-4851-B512-372380949518}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs index 963aa61..6e7591e 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest.Android/Resources/Resource.designer.cs @@ -14,7 +14,7 @@ namespace ValidationRulesTest.Droid { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.3.99.43")] public partial class Resource { diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj index 71c7ed3..a99db12 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ValidationRulesTest.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -14,10 +14,14 @@ + + + ReactiveValidationExample1.xaml + Example1.xaml diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs new file mode 100644 index 0000000..02814d9 --- /dev/null +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/ViewModels/ReactiveValidationExample1ViewModel.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Plugin.Reactive.ValidationRules; +using Plugin.ValidationRules.Extensions; +using Plugin.ValidationRules.Interfaces; +using Plugin.ValidationRules.Rules; +using ValidationRulesTest.Validations; +using EmailRule = Plugin.ValidationRules.Rules.EmailRule; + +namespace ValidationRulesTest.ViewModels +{ + public class ReactiveValidationExample1ViewModel : ExtendedPropertyChanged, IDisposable + { + ReactiveValidatable _name; + public ReactiveValidatable Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + public ReactiveValidatable _email; + public ReactiveValidatable Email + { + get => _email; + set => SetProperty(ref _email, value); + } + + public ReactiveValidatable _password; + public ReactiveValidatable Password + { + get => _password; + set => SetProperty(ref _password, value); + } + + public ReactiveValidationExample1ViewModel() + { + Name = new ReactiveValidatable( + string.Empty, new NotEmptyRule(string.Empty) + { + ValidationMessage = "Name is required" + }); + + Email = new ReactiveValidatable(string.Empty, + new EmailRule() + { + ValidationMessage = "Email wrongly formatted" + }, + new NotEmptyRule(string.Empty) + { + ValidationMessage = "Email is required" + }); + + Password = new ReactiveValidatable(string.Empty, new PasswordRule(), new NotEmptyRule(string.Empty) + { + ValidationMessage = "Password is required" + }); + } + + public void Dispose() + { + _name?.Dispose(); + _email?.Dispose(); + _password?.Dispose(); + } + } +} \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml index 9db1620..dc8f8d1 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Example1.xaml @@ -1,10 +1,9 @@  - + diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml index 9ecd20f..0504edd 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml @@ -51,6 +51,12 @@ x:Name="example8" Clicked="example8_Clicked" Text="Example 8" /> + + diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs index ba2373f..370a280 100644 --- a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/Examples.xaml.cs @@ -56,5 +56,10 @@ private void example8_Clicked(object sender, EventArgs e) { Navigation.PushAsync(new Example8()); } + + private void ReactiveValidationExample1_Clicked(object sender, EventArgs e) + { + Navigation.PushAsync(new ReactiveValidationExample1()); + } } } \ No newline at end of file diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml new file mode 100644 index 0000000..cc51424 --- /dev/null +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs new file mode 100644 index 0000000..f193cec --- /dev/null +++ b/samples/Xamarin.Forms/ValidationRulesTest/ValidationRulesTest/Views/ReactiveValidationExample1.xaml.cs @@ -0,0 +1,24 @@ +using System; +using Xamarin.Forms; +using ValidationRulesTest.ViewModels; + +namespace ValidationRulesTest +{ + public partial class ReactiveValidationExample1 : ContentPage + { + ReactiveValidationExample1ViewModel _context; + public ReactiveValidationExample1() + { + InitializeComponent(); + + _context = new ReactiveValidationExample1ViewModel(); + BindingContext = _context; + } + + protected override void OnDisappearing() + { + _context.Dispose(); + base.OnDisappearing(); + } + } +} diff --git a/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj new file mode 100644 index 0000000..d5d6eca --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/Plugin.Reactive.ValidationRules.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0;net6.0 + + + + + + + + + + + + + + + diff --git a/src/Plugin.Reactive.ValidationRules/README.MD b/src/Plugin.Reactive.ValidationRules/README.MD new file mode 100644 index 0000000..8a3bc0e --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/README.MD @@ -0,0 +1,4 @@ +# Reactive Validation Rules +This plugin is an extension of the Validation rules plugin, with +a reactive programming approach to validation. It leverages +Reactive UI to perform input validation. diff --git a/src/Plugin.Reactive.ValidationRules/ReactiveRules/BaseReactiveRule.cs b/src/Plugin.Reactive.ValidationRules/ReactiveRules/BaseReactiveRule.cs new file mode 100644 index 0000000..6d553fb --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/ReactiveRules/BaseReactiveRule.cs @@ -0,0 +1,7 @@ +namespace Plugin.Reactive.ValidationRules.ReactiveRules +{ + public class BaseReactiveRule + { + + } +} \ No newline at end of file diff --git a/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs new file mode 100644 index 0000000..65aad4c --- /dev/null +++ b/src/Plugin.Reactive.ValidationRules/ReactiveValidatable.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Plugin.ValidationRules.Interfaces; +using ReactiveUI; + +namespace Plugin.Reactive.ValidationRules +{ + /// + /// A validatable instance, that leverages the power of + /// reactive programming to automatically validate its value when + /// a change is made to it, and generates error messages based on + /// the validation rules it is given. + /// + /// + public class ReactiveValidatable : ReactiveObject, IValidity, IDisposable + { + public List> _validations; + protected IDisposable _valueDisposable; + private bool _disposed; + + private T _value; + /// + /// The value to be validated. + /// NOTE: This object automatically validates + /// this value when it changes. No check has to + /// be done by external code. + /// + public T Value + { + get { return _value; } + set { this.RaiseAndSetIfChanged(ref _value, value); } + } + + private bool _isValid; + /// + /// Determines if the Value is valid or not. + /// + public bool IsValid + { + get { return _isValid; } + set { this.RaiseAndSetIfChanged(ref _isValid, value); } + } + + private string _errorText; + /// + /// A simple error message displayed by + /// the UI when validation fails. + /// + public string ErrorMessage + { + get { return _errorText; } + set { this.RaiseAndSetIfChanged(ref _errorText, value); } + } + private List _errors; + /// + /// List of errors users have before can save the record + /// + public List Errors + { + get => _errors; + set + { + Error = value?.Count > 0 ? value.FirstOrDefault() : string.Empty; + this.RaiseAndSetIfChanged(ref _errors, value); + } + } + private bool _hasErrors; + /// + /// The value indicating whether the validation has errors. + /// + public bool HasErrors + { + get => _hasErrors; + set => this.RaiseAndSetIfChanged(ref _hasErrors, value); + } + public string Error { get; set; } + /// + /// Formats the list of error messages + /// into a simple error message understandable + /// in one line. + /// + public Func, string> ErrorMessageFormatter { get; set; } + + /// + /// Creates a new instance of the validatable object. + /// + public ReactiveValidatable() + { + ErrorMessageFormatter = (errorMessages) => + { + if (errorMessages != null && errorMessages.Any()) + { + var builder = new StringBuilder(); + for (int i = 0; i < errorMessages.Count;i++) + { + builder.Append(errorMessages[i]); + if (i < errorMessages.Count - 1) + { + builder.Append(Environment.NewLine); + } + } + + return builder.ToString(); + } + + return string.Empty; + }; + + _validations = new List>(); + Errors = new List(); + _valueDisposable = this.WhenAnyValue(x => x.Value).Subscribe(x => + { + Validate(); + }); + } + + /// + /// Create an instance of a reactive validatable object. + /// + /// The value to be watched and validated. + public ReactiveValidatable(T value) : this() + { + Value = value; + } + + /// + /// Create an instance of a reactive validatable object. + /// + /// The value to be watched and validated. + /// The validation rules that the value should be checked for. + public ReactiveValidatable(T value, params IValidationRule[] validations) : this(value) + { + _validations.AddRange(validations); + } + + /// + /// Create an instance of a reactive validatable object. + /// + /// The value to be watched and validated. + /// A function to format the list of errors to be displayed in a single string on the UI. + /// The validation rules that the value should be checked for. + public ReactiveValidatable(T value, Func, string> errorMessageFormatter, + params IValidationRule[] validations) : this(value, validations) + { + ErrorMessageFormatter = errorMessageFormatter; + } + + /// + /// Validate the value of this property + /// each time it changes. + /// + /// + public bool Validate() + { + IsValid = TryValidate(Value); + if (Errors.Any()) + { + HasErrors = true; + ErrorMessage = ErrorMessageFormatter(Errors); + } + else + { + ErrorMessage = string.Empty; + } + return IsValid; + } + + /// + /// Tells if the object is valid without + /// setting its state + /// + /// + public bool TryValidate(T val) + { + Errors.Clear(); + Errors = new List(_validations.Where(v => !v.Check(val)) + .Select(v => v.ValidationMessage)); + + return !Errors.Any(); + } + + #region Disposing + + private void ReleaseManagedResources() + { + // Release resources + _validations?.Clear(); + _errors?.Clear(); + _value = default(T); + _valueDisposable.Dispose(); + } + + public void Dispose() + { + // If this function is being called the user wants to release the + // resources. lets call the Dispose which will do this for us. + Dispose(true); + + // Now since we have done the cleanup already there is nothing left + // for the Finalizer to do. So lets tell the GC not to call it later. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + ReleaseManagedResources(); + } + + _disposed = true; + } + } + + ~ReactiveValidatable() + { + Dispose(false); + } + #endregion + + } +} diff --git a/src/Plugin.ValidationRules.sln b/src/Plugin.ValidationRules.sln new file mode 100644 index 0000000..af5c125 --- /dev/null +++ b/src/Plugin.ValidationRules.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32228.343 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ValidationRules", "ValidationRules\Plugin.ValidationRules.csproj", "{424D34D8-A9A4-49F2-B958-324C41AA66C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.Reactive.ValidationRules", "Plugin.Reactive.ValidationRules\Plugin.Reactive.ValidationRules.csproj", "{1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {424D34D8-A9A4-49F2-B958-324C41AA66C2}.Release|Any CPU.Build.0 = Release|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ABEA0B9-9EA6-42DD-9EA7-8B5F7542E1E9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {599BD14E-621D-4CC6-BF59-F8DD48595F45} + EndGlobalSection +EndGlobal