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