From 76f5249f5912d569c140bd5734c83654ba9370e0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 14 Apr 2016 00:51:50 -0500 Subject: [PATCH] Created modular validation plugin system instead of being hard-coded to just INotifyDataErrorInfo. Current plugins are for Exception validation and INotifyDataErrorInfo. Currently not in use anywhere. --- .../ExceptionValidationCheckerPlugin.cs | 52 +++++++++++++ .../Data/Plugins/IValidationCheckerPlugin.cs | 14 ++++ .../Plugins/IndeiValidationCheckerPlugin.cs | 73 +++++++++++++++++++ .../Data/Plugins/ValidationCheckerBase.cs | 34 +++++++++ .../Perspex.Markup/Perspex.Markup.csproj | 4 + src/Perspex.Base/Data/ValidationStatus.cs | 20 +++++ src/Perspex.Base/Perspex.Base.csproj | 1 + .../Data/InpcPluginTests.cs | 8 +- 8 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 src/Markup/Perspex.Markup/Data/Plugins/ExceptionValidationCheckerPlugin.cs create mode 100644 src/Markup/Perspex.Markup/Data/Plugins/IValidationCheckerPlugin.cs create mode 100644 src/Markup/Perspex.Markup/Data/Plugins/IndeiValidationCheckerPlugin.cs create mode 100644 src/Markup/Perspex.Markup/Data/Plugins/ValidationCheckerBase.cs create mode 100644 src/Perspex.Base/Data/ValidationStatus.cs diff --git a/src/Markup/Perspex.Markup/Data/Plugins/ExceptionValidationCheckerPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/ExceptionValidationCheckerPlugin.cs new file mode 100644 index 0000000000..dc8b9b18ad --- /dev/null +++ b/src/Markup/Perspex.Markup/Data/Plugins/ExceptionValidationCheckerPlugin.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perspex.Data; + +namespace Perspex.Markup.Data.Plugins +{ + public class ExceptionValidationCheckerPlugin : IValidationCheckerPlugin + { + public ValidationCheckerBase Start(WeakReference reference, string name, IPropertyAccessor accessor, Action callback) + { + return new ExceptionValidationChecker(reference, name, accessor, callback); + } + + private class ExceptionValidationChecker : ValidationCheckerBase + { + public ExceptionValidationChecker(WeakReference reference, string name, IPropertyAccessor accessor, Action callback) + : base(reference, name, accessor, callback) + { + } + + public override bool SetValue(object value, BindingPriority priority) + { + try + { + var success = base.SetValue(value, priority); + SendValidationCallback(new ExceptionValidationStatus(null)); + return success; + } + catch (Exception ex) + { + SendValidationCallback(new ExceptionValidationStatus(ex)); + } + return false; + } + } + + private class ExceptionValidationStatus : ValidationStatus + { + public ExceptionValidationStatus(Exception exception) + { + Exception = exception; + } + + public Exception Exception { get; } + + public override bool IsValid => Exception != null; + } + } +} diff --git a/src/Markup/Perspex.Markup/Data/Plugins/IValidationCheckerPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/IValidationCheckerPlugin.cs new file mode 100644 index 0000000000..07a10f235f --- /dev/null +++ b/src/Markup/Perspex.Markup/Data/Plugins/IValidationCheckerPlugin.cs @@ -0,0 +1,14 @@ +using Perspex.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Markup.Data.Plugins +{ + public interface IValidationCheckerPlugin + { + ValidationCheckerBase Start(WeakReference reference, string name, IPropertyAccessor accessor, Action callback); + } +} diff --git a/src/Markup/Perspex.Markup/Data/Plugins/IndeiValidationCheckerPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/IndeiValidationCheckerPlugin.cs new file mode 100644 index 0000000000..dfd63ef2b6 --- /dev/null +++ b/src/Markup/Perspex.Markup/Data/Plugins/IndeiValidationCheckerPlugin.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perspex.Data; +using System.ComponentModel; +using System.Collections; +using Perspex.Utilities; + +namespace Perspex.Markup.Data.Plugins +{ + class IndeiValidationCheckerPlugin : IValidationCheckerPlugin + { + public ValidationCheckerBase Start(WeakReference reference, string name, IPropertyAccessor accessor, Action callback) + { + return new IndeiValidationChecker(reference, name, accessor, callback); + } + + private class IndeiValidationChecker : ValidationCheckerBase, IWeakSubscriber + { + public IndeiValidationChecker(WeakReference reference, string name, IPropertyAccessor accessor, Action callback) + : base(reference, name, accessor, callback) + { + var target = reference.Target as INotifyDataErrorInfo; + if (target != null) + { + if (target.HasErrors) + { + SendValidationCallback(new IndeiValidationStatus(target.GetErrors(name))); + } + WeakSubscriptionManager.Subscribe( + target, + nameof(target.ErrorsChanged), + this); + } + } + + public override void Dispose() + { + base.Dispose(); + var target = _reference.Target as INotifyDataErrorInfo; + if (target != null) + { + WeakSubscriptionManager.Unsubscribe( + target, + nameof(target.ErrorsChanged), + this); + } + } + + public void OnEvent(object sender, DataErrorsChangedEventArgs e) + { + if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName)) + { + var indei = _reference.Target as INotifyDataErrorInfo; + SendValidationCallback(new IndeiValidationStatus(indei.GetErrors(e.PropertyName))); + } + } + } + + private class IndeiValidationStatus : ValidationStatus + { + public IndeiValidationStatus(IEnumerable errors) + { + Errors = errors; + } + public override bool IsValid => !Errors.OfType().Any(); + + public IEnumerable Errors { get; } + } + } +} diff --git a/src/Markup/Perspex.Markup/Data/Plugins/ValidationCheckerBase.cs b/src/Markup/Perspex.Markup/Data/Plugins/ValidationCheckerBase.cs new file mode 100644 index 0000000000..8ab7b7e43e --- /dev/null +++ b/src/Markup/Perspex.Markup/Data/Plugins/ValidationCheckerBase.cs @@ -0,0 +1,34 @@ +using System; +using Perspex.Data; + +namespace Perspex.Markup.Data.Plugins +{ + public abstract class ValidationCheckerBase : IPropertyAccessor + { + protected readonly WeakReference _reference; + protected readonly string _name; + private readonly IPropertyAccessor _accessor; + private readonly Action _callback; + + protected ValidationCheckerBase(WeakReference reference, string name, IPropertyAccessor accessor, Action callback) + { + _reference = reference; + _name = name; + _accessor = accessor; + _callback = callback; + } + + public Type PropertyType => _accessor.PropertyType; + + public object Value => _accessor.Value; + + public virtual void Dispose() => _accessor.Dispose(); + + public virtual bool SetValue(object value, BindingPriority priority) => _accessor.SetValue(value, priority); + + protected void SendValidationCallback(ValidationStatus status) + { + _callback?.Invoke(status); + } + } +} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup/Perspex.Markup.csproj b/src/Markup/Perspex.Markup/Perspex.Markup.csproj index 5427d97346..443991b120 100644 --- a/src/Markup/Perspex.Markup/Perspex.Markup.csproj +++ b/src/Markup/Perspex.Markup/Perspex.Markup.csproj @@ -46,6 +46,10 @@ + + + + diff --git a/src/Perspex.Base/Data/ValidationStatus.cs b/src/Perspex.Base/Data/ValidationStatus.cs new file mode 100644 index 0000000000..9effaa8239 --- /dev/null +++ b/src/Perspex.Base/Data/ValidationStatus.cs @@ -0,0 +1,20 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Data +{ + /// + /// Contains information on if the current object passed validation. + /// Subclasses of this class contain additional information depending on the method of validation checking. + /// + public abstract class ValidationStatus + { + public abstract bool IsValid { get; } + } +} diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index df240ee3b8..cc8cb6ada6 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -44,6 +44,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs b/tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs index 1c97d5c386..74bd6bd7b3 100644 --- a/tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs +++ b/tests/Perspex.Markup.UnitTests/Data/InpcPluginTests.cs @@ -19,7 +19,11 @@ namespace Perspex.Markup.UnitTests.Data public int NoValidationTest { get { return noValidationTest; } - set { noValidationTest = value; NotifyPropertyChanged(); } + set + { + noValidationTest = value; + NotifyPropertyChanged(); + } } public bool HasErrors @@ -79,7 +83,7 @@ namespace Perspex.Markup.UnitTests.Data var plugin = new InpcPropertyAccessorPlugin(); var source = new InpcTest { NoValidationTest = 0 }; var changeFired = false; - plugin.Start(new WeakReference(source), nameof(InpcTest.NoValidationTest), _ => changeFired = true, _ => { }); + var accessor = plugin.Start(new WeakReference(source), nameof(InpcTest.NoValidationTest), _ => changeFired = true, _ => { }); source.NoValidationTest = 1; Assert.True(changeFired);