From c69e2f12edf4eb8dccefb657d58cb4e996333455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 11 Apr 2020 17:01:04 +0300 Subject: [PATCH] Allow to write custom code to validate an extensible object. --- .../ExtensibleObjectValidator.cs | 79 +++++++++++++++++-- .../ObjectExtending/ObjectExtensionInfo.cs | 4 + .../ObjectExtensionPropertyInfo.cs | 4 + ...bjectExtensionPropertyValidationContext.cs | 61 ++++++++++++++ .../ObjectExtensionValidationContext.cs | 53 +++++++++++++ .../ExtensibleObjectValidator_Tests.cs | 73 +++++++++++++++-- 6 files changed, 263 insertions(+), 11 deletions(-) create mode 100644 framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs create mode 100644 framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs index f29f334a8d..0c60f59699 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs @@ -52,20 +52,47 @@ namespace Volo.Abp.ObjectExtending return; } - foreach (var propertyInfo in objectExtensionInfo.GetProperties()) + AddPropertyValidationErrors( + extensibleObject, + validationErrors, + objectValidationContext, + objectExtensionInfo + ); + + ExecuteCustomObjectValidationActions( + extensibleObject, + validationErrors, + objectValidationContext, + objectExtensionInfo + ); + } + + private static void AddPropertyValidationErrors( + IHasExtraProperties extensibleObject, + List validationErrors, + ValidationContext objectValidationContext, + ObjectExtensionInfo objectExtensionInfo) + { + var properties = objectExtensionInfo.GetProperties(); + if (!properties.Any()) + { + return; + } + + foreach (var property in properties) { - if (propertyInfo.ValidationAttributes.Any()) + if (property.ValidationAttributes.Any()) { var propertyValidationContext = new ValidationContext(extensibleObject, objectValidationContext, null) { - DisplayName = propertyInfo.Name, - MemberName = propertyInfo.Name + DisplayName = property.Name, + MemberName = property.Name }; - foreach (var attribute in propertyInfo.ValidationAttributes) + foreach (var attribute in property.ValidationAttributes) { var result = attribute.GetValidationResult( - extensibleObject.GetProperty(propertyInfo.Name), + extensibleObject.GetProperty(property.Name), propertyValidationContext ); if (result != null) @@ -74,6 +101,46 @@ namespace Volo.Abp.ObjectExtending } } } + + if (property.Validators.Any()) + { + var context = new ObjectExtensionPropertyValidationContext( + property, + extensibleObject, + validationErrors, + objectValidationContext, + extensibleObject.GetProperty(property.Name) + ); + + foreach (var validator in property.Validators) + { + validator(context); + } + } + } + } + + private static void ExecuteCustomObjectValidationActions( + IHasExtraProperties extensibleObject, + List validationErrors, + ValidationContext objectValidationContext, + ObjectExtensionInfo objectExtensionInfo) + { + if (!objectExtensionInfo.Validators.Any()) + { + return; + } + + var context = new ObjectExtensionValidationContext( + objectExtensionInfo, + extensibleObject, + validationErrors, + objectValidationContext + ); + + foreach (var validator in objectExtensionInfo.Validators) + { + validator(context); } } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs index 89ecbab574..2159e06d5e 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs @@ -17,11 +17,15 @@ namespace Volo.Abp.ObjectExtending [NotNull] public Dictionary Configuration { get; } + [NotNull] + public List> Validators { get; } + public ObjectExtensionInfo([NotNull] Type type) { Type = Check.AssignableTo(type, nameof(type)); Properties = new Dictionary(); Configuration = new Dictionary(); + Validators = new List>(); } public virtual bool HasProperty(string propertyName) diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs index ce490b6264..e6dbbfd6ce 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs @@ -19,6 +19,9 @@ namespace Volo.Abp.ObjectExtending [NotNull] public List ValidationAttributes { get; } + [NotNull] + public List> Validators { get; } + /// /// Indicates whether to check the other side of the object mapping /// if it explicitly defines the property. This property is used in; @@ -47,6 +50,7 @@ namespace Volo.Abp.ObjectExtending Configuration = new Dictionary(); ValidationAttributes = new List(); + Validators = new List>(); } } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs new file mode 100644 index 0000000000..ab2a7ccb38 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyValidationContext.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using Volo.Abp.Data; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionPropertyValidationContext + { + /// + /// Related property extension information. + /// + [NotNull] + public ObjectExtensionPropertyInfo ExtensionPropertyInfo { get; } + + /// + /// Reference to the validating object. + /// + [NotNull] + public IHasExtraProperties ValidatingObject { get; } + + /// + /// Add validation errors to this list. + /// + [NotNull] + public List ValidationErrors { get; } + + /// + /// Validation context comes from the method. + /// + [NotNull] + public ValidationContext ValidationContext { get; } + + /// + /// The value of the validating property of the . + /// + [CanBeNull] + public object Value { get; } + + /// + /// Can be used to resolve services from the dependency injection container. + /// + [CanBeNull] + public IServiceProvider ServiceProvider => ValidationContext; + + public ObjectExtensionPropertyValidationContext( + [NotNull] ObjectExtensionPropertyInfo objectExtensionPropertyInfo, + [NotNull] IHasExtraProperties validatingObject, + [NotNull] List validationErrors, + [NotNull] ValidationContext validationContext, + [CanBeNull] object value) + { + ExtensionPropertyInfo = Check.NotNull(objectExtensionPropertyInfo, nameof(objectExtensionPropertyInfo)); + ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject)); + ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors)); + ValidationContext = Check.NotNull(validationContext, nameof(validationContext)); + Value = value; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs new file mode 100644 index 0000000000..ce10f3fe3c --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionValidationContext.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using Volo.Abp.Data; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionValidationContext + { + /// + /// Related object extension information. + /// + [NotNull] + public ObjectExtensionInfo ObjectExtensionInfo { get; } + + /// + /// Reference to the validating object. + /// + [NotNull] + public IHasExtraProperties ValidatingObject { get; } + + /// + /// Add validation errors to this list. + /// + [NotNull] + public List ValidationErrors { get; } + + /// + /// Validation context comes from the method. + /// + [NotNull] + public ValidationContext ValidationContext { get; } + + /// + /// Can be used to resolve services from the dependency injection container. + /// + [CanBeNull] + public IServiceProvider ServiceProvider => ValidationContext; + + public ObjectExtensionValidationContext( + [NotNull] ObjectExtensionInfo objectExtensionInfo, + [NotNull] IHasExtraProperties validatingObject, + [NotNull] List validationErrors, + [NotNull] ValidationContext validationContext) + { + ObjectExtensionInfo = Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject)); + ValidationErrors = Check.NotNull(validationErrors, nameof(validationErrors)); + ValidationContext = Check.NotNull(validationContext, nameof(validationContext)); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs index 35c419e7d8..8c27844e22 100644 --- a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs @@ -38,6 +38,43 @@ namespace Volo.Abp.ObjectExtending { }); + + options.AddOrUpdateProperty("Password", propertyInfo => + { + }); + + options.AddOrUpdateProperty("PasswordRepeat", propertyInfo => + { + propertyInfo.Validators.Add(context => + { + if (context.ValidatingObject.HasProperty("Password")) + { + if (context.ValidatingObject.GetProperty("Password") != + context.Value as string) + { + context.ValidationErrors.Add( + new ValidationResult( + "If you specify a password, then please correctly repeat it!", + new[] {"Password", "PasswordRepeat"} + ) + ); + } + } + }); + }); + + options.Validators.Add(context => + { + if (context.ValidatingObject.GetProperty("Name") == "BadValue") + { + context.ValidationErrors.Add( + new ValidationResult( + "Name can not be 'BadValue', sorry :(", + new[] { "Name" } + ) + ); + } + }); }); }); } @@ -64,7 +101,7 @@ namespace Volo.Abp.ObjectExtending ExtensibleObjectValidator .GetValidationErrors( new ExtensiblePersonObject() - ).Count.ShouldBe(2); //Name & Age + ).Count.ShouldBe(2); // Name & Age ExtensibleObjectValidator .GetValidationErrors( @@ -75,7 +112,7 @@ namespace Volo.Abp.ObjectExtending {"Address", new string('x', 256) } } } - ).Count.ShouldBe(3); //Name, Age & Address + ).Count.ShouldBe(3); // Name, Age & Address ExtensibleObjectValidator .GetValidationErrors( @@ -86,7 +123,7 @@ namespace Volo.Abp.ObjectExtending {"Age", "42" } } } - ).Count.ShouldBe(1); //Name + ).Count.ShouldBe(1); // Name ExtensibleObjectValidator .GetValidationErrors( @@ -97,8 +134,34 @@ namespace Volo.Abp.ObjectExtending {"Address", new string('x', 256) }, {"Age", "100" } } - } - ).Count.ShouldBe(3); //Name, Age & Address + } + ).Count.ShouldBe(3); // Name, Age & Address + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Name", "John"}, + {"Age", "42"}, + {"Password", "123"}, + {"PasswordRepeat", "1256"} + } + } + ).Count.ShouldBe(1); // PasswordRepeat != Password + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Name", "BadValue"}, + {"Age", "42"}, + } + } + ).Count.ShouldBe(1); //Name is 'BadValue'! } private class ExtensiblePersonObject : ExtensibleObject