From 85f1fc5496144dea0f58f1cc3f450e08f633f6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 10 Apr 2020 22:21:28 +0300 Subject: [PATCH 1/7] Create ObjectExtensionPropertyInfoValidationExtensions. --- ...tensionPropertyInfoValidationExtensions.cs | 68 +++++++++++++++++++ ...tExtensionPropertyInfoValidationOptions.cs | 25 +++++++ .../Abp/Validation/AbpValidationModule.cs | 1 + 3 files changed, 94 insertions(+) create mode 100644 framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs create mode 100644 framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs new file mode 100644 index 0000000000..926e086d21 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; + +namespace Volo.Abp.ObjectExtending +{ + public static class ObjectExtensionPropertyInfoValidationExtensions + { + public const string ValidationConfigurationName = "Validation"; + + [NotNull] + public static ObjectExtensionPropertyInfo ConfigureValidation( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension, + [NotNull] Action configureAction) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + Check.NotNull(configureAction, nameof(configureAction)); + + configureAction(propertyExtension.GetOrAddValidationConfiguration()); + + return propertyExtension; + } + + [NotNull] + public static ObjectExtensionPropertyInfo AddValidationAttributes( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension, + [NotNull] params ValidationAttribute[] validationAttributes) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + Check.NotNullOrEmpty(validationAttributes, nameof(validationAttributes)); + + propertyExtension + .GetOrAddValidationConfiguration() + .ValidationAttributes + .AddRange(validationAttributes); + + return propertyExtension; + } + + [CanBeNull] + public static ObjectExtensionPropertyInfoValidationOptions GetValidationConfigurationOrNull( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + return propertyExtension.Configuration.GetOrDefault(ValidationConfigurationName) + as ObjectExtensionPropertyInfoValidationOptions; + } + + [NotNull] + public static ObjectExtensionPropertyInfoValidationOptions GetOrAddValidationConfiguration( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + var validationConfiguration = propertyExtension.GetValidationConfigurationOrNull(); + + if (validationConfiguration == null) + { + validationConfiguration = new ObjectExtensionPropertyInfoValidationOptions(propertyExtension); + propertyExtension.Configuration[ValidationConfigurationName] = validationConfiguration; + } + + return validationConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs new file mode 100644 index 0000000000..e28efc14df --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionPropertyInfoValidationOptions + { + [NotNull] + public ObjectExtensionPropertyInfo ExtensionProperty { get; } + + [NotNull] + public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension; + + [NotNull] + public List ValidationAttributes { get; } + + public ObjectExtensionPropertyInfoValidationOptions( + [NotNull] ObjectExtensionPropertyInfo extensionProperty) + { + ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty)); + ValidationAttributes = new List(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs index ad2be911de..9da6766a5d 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs @@ -18,6 +18,7 @@ namespace Volo.Abp.Validation context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded); AutoAddObjectValidationContributors(context.Services); } + public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => From 2b211c1073e8ab55caa08384c5fd642af7f14f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 11 Apr 2020 16:17:21 +0300 Subject: [PATCH 2/7] Add validation for extra properties. --- .../Volo/Abp/Domain/Entities/AggregateRoot.cs | 18 +++ .../Abp/Data/HasExtraPropertiesExtensions.cs | 13 ++- .../Volo/Abp/Data/IHasExtraProperties.cs | 2 + .../Abp/ObjectExtending/ExtensibleObject.cs | 11 +- .../ExtensibleObjectValidator.cs | 75 ++++++++++++ .../ObjectExtensionPropertyInfo.cs | 5 + ...tensionPropertyInfoValidationExtensions.cs | 68 ----------- ...tExtensionPropertyInfoValidationOptions.cs | 25 ---- .../ExtensibleObjectValidator_Tests.cs | 109 ++++++++++++++++++ .../HasExtraPropertiesExtensions_Tests.cs | 4 + 10 files changed, 231 insertions(+), 99 deletions(-) create mode 100644 framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs delete mode 100644 framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs delete mode 100644 framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs create mode 100644 framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs index 5d2ad56642..be02f3d398 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; using Volo.Abp.Auditing; using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; namespace Volo.Abp.Domain.Entities { @@ -56,6 +58,14 @@ namespace Volo.Abp.Domain.Entities { _distributedEvents.Clear(); } + + public virtual IEnumerable Validate(ValidationContext validationContext) + { + return ExtensibleObjectValidator.GetValidationErrors( + this, + validationContext + ); + } } [Serializable] @@ -115,5 +125,13 @@ namespace Volo.Abp.Domain.Entities { _distributedEvents.Clear(); } + + public virtual IEnumerable Validate(ValidationContext validationContext) + { + return ExtensibleObjectValidator.GetValidationErrors( + this, + validationContext + ); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs index 15915cc94b..2b75e5fc8f 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs @@ -5,6 +5,8 @@ using Volo.Abp.Reflection; namespace Volo.Abp.Data { + //TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0 + public static class HasExtraPropertiesExtensions { public static bool HasProperty(this IHasExtraProperties source, string name) @@ -12,17 +14,18 @@ namespace Volo.Abp.Data return source.ExtraProperties.ContainsKey(name); } - public static object GetProperty(this IHasExtraProperties source, string name) + public static object GetProperty(this IHasExtraProperties source, string name, object defaultValue = null) { - return source.ExtraProperties?.GetOrDefault(name); + return source.ExtraProperties?.GetOrDefault(name) + ?? defaultValue; } - public static TProperty GetProperty(this IHasExtraProperties source, string name) + public static TProperty GetProperty(this IHasExtraProperties source, string name, TProperty defaultValue = default) { var value = source.GetProperty(name); - if (value == default) + if (value == null) { - return default; + return defaultValue; } if (TypeHelper.IsPrimitiveExtended(typeof(TProperty), includeEnums: true)) diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs index ccdb0b4137..1ba519680c 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs @@ -2,6 +2,8 @@ namespace Volo.Abp.Data { + //TODO: Move to Volo.Abp.Data.ObjectExtending namespace at v3.0 + public interface IHasExtraProperties { Dictionary ExtraProperties { get; } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs index 07243c9dca..c9c601fce9 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Volo.Abp.Data; namespace Volo.Abp.ObjectExtending { [Serializable] - public class ExtensibleObject : IHasExtraProperties + public class ExtensibleObject : IHasExtraProperties, IValidatableObject { public Dictionary ExtraProperties { get; protected set; } @@ -13,5 +14,13 @@ namespace Volo.Abp.ObjectExtending { ExtraProperties = new Dictionary(); } + + public virtual IEnumerable Validate(ValidationContext validationContext) + { + return ExtensibleObjectValidator.GetValidationErrors( + this, + validationContext + ); + } } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs new file mode 100644 index 0000000000..9b48798972 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using Volo.Abp.Data; +using Volo.Abp.DynamicProxy; + +namespace Volo.Abp.ObjectExtending +{ + public static class ExtensibleObjectValidator + { + [NotNull] + public static List GetValidationErrors( + [NotNull] IHasExtraProperties extensibleObject, + [CanBeNull] IServiceProvider serviceProvider = null) + { + var validationErrors = new List(); + + AddValidationErrors( + extensibleObject, + new ValidationContext( + extensibleObject, + serviceProvider, + new Dictionary() + ), + validationErrors + ); + + return validationErrors; + } + + public static void AddValidationErrors( + [NotNull] IHasExtraProperties extensibleObject, + [NotNull] ValidationContext validationContext, + [NotNull] List validationErrors) + { + Check.NotNull(extensibleObject, nameof(extensibleObject)); + Check.NotNull(validationContext, nameof(validationContext)); + Check.NotNull(validationErrors, nameof(validationErrors)); + + var objectType = ProxyHelper.UnProxy(extensibleObject).GetType(); + + var objectExtensionInfo = ObjectExtensionManager.Instance + .GetOrNull(objectType); + + if (objectExtensionInfo == null) + { + return; + } + + foreach (var propertyInfo in objectExtensionInfo.GetProperties()) + { + if (propertyInfo.ValidationAttributes.IsNullOrEmpty()) + { + continue; + } + + var validationContext2 = new ValidationContext(extensibleObject, validationContext, null) + { + DisplayName = propertyInfo.Name, + MemberName = propertyInfo.Name + }; + + foreach (var attribute in propertyInfo.ValidationAttributes) + { + var result = attribute.GetValidationResult(extensibleObject.GetProperty(propertyInfo.Name), validationContext2); + if (result != null) + { + validationErrors.Add(result); + } + } + } + } + } +} \ No newline at end of file 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 2edae9a3f8..ce490b6264 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; namespace Volo.Abp.ObjectExtending @@ -15,6 +16,9 @@ namespace Volo.Abp.ObjectExtending [NotNull] public Type Type { get; } + [NotNull] + public List ValidationAttributes { get; } + /// /// Indicates whether to check the other side of the object mapping /// if it explicitly defines the property. This property is used in; @@ -42,6 +46,7 @@ namespace Volo.Abp.ObjectExtending Name = Check.NotNull(name, nameof(name)); Configuration = new Dictionary(); + ValidationAttributes = new List(); } } } diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs deleted file mode 100644 index 926e086d21..0000000000 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; - -namespace Volo.Abp.ObjectExtending -{ - public static class ObjectExtensionPropertyInfoValidationExtensions - { - public const string ValidationConfigurationName = "Validation"; - - [NotNull] - public static ObjectExtensionPropertyInfo ConfigureValidation( - [NotNull] this ObjectExtensionPropertyInfo propertyExtension, - [NotNull] Action configureAction) - { - Check.NotNull(propertyExtension, nameof(propertyExtension)); - Check.NotNull(configureAction, nameof(configureAction)); - - configureAction(propertyExtension.GetOrAddValidationConfiguration()); - - return propertyExtension; - } - - [NotNull] - public static ObjectExtensionPropertyInfo AddValidationAttributes( - [NotNull] this ObjectExtensionPropertyInfo propertyExtension, - [NotNull] params ValidationAttribute[] validationAttributes) - { - Check.NotNull(propertyExtension, nameof(propertyExtension)); - Check.NotNullOrEmpty(validationAttributes, nameof(validationAttributes)); - - propertyExtension - .GetOrAddValidationConfiguration() - .ValidationAttributes - .AddRange(validationAttributes); - - return propertyExtension; - } - - [CanBeNull] - public static ObjectExtensionPropertyInfoValidationOptions GetValidationConfigurationOrNull( - [NotNull] this ObjectExtensionPropertyInfo propertyExtension) - { - Check.NotNull(propertyExtension, nameof(propertyExtension)); - - return propertyExtension.Configuration.GetOrDefault(ValidationConfigurationName) - as ObjectExtensionPropertyInfoValidationOptions; - } - - [NotNull] - public static ObjectExtensionPropertyInfoValidationOptions GetOrAddValidationConfiguration( - [NotNull] this ObjectExtensionPropertyInfo propertyExtension) - { - Check.NotNull(propertyExtension, nameof(propertyExtension)); - - var validationConfiguration = propertyExtension.GetValidationConfigurationOrNull(); - - if (validationConfiguration == null) - { - validationConfiguration = new ObjectExtensionPropertyInfoValidationOptions(propertyExtension); - propertyExtension.Configuration[ValidationConfigurationName] = validationConfiguration; - } - - return validationConfiguration; - } - } -} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs deleted file mode 100644 index e28efc14df..0000000000 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoValidationOptions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; - -namespace Volo.Abp.ObjectExtending -{ - public class ObjectExtensionPropertyInfoValidationOptions - { - [NotNull] - public ObjectExtensionPropertyInfo ExtensionProperty { get; } - - [NotNull] - public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension; - - [NotNull] - public List ValidationAttributes { get; } - - public ObjectExtensionPropertyInfoValidationOptions( - [NotNull] ObjectExtensionPropertyInfo extensionProperty) - { - ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty)); - ValidationAttributes = new List(); - } - } -} \ 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 new file mode 100644 index 0000000000..35c419e7d8 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs @@ -0,0 +1,109 @@ +using System.ComponentModel.DataAnnotations; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Threading; +using Xunit; + +namespace Volo.Abp.ObjectExtending +{ + public class ExtensibleObjectValidator_Tests + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + static ExtensibleObjectValidator_Tests() + { + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance + .AddOrUpdate(options => + { + options.AddOrUpdateProperty("Name", propertyInfo => + { + propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); + propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(64) { MinimumLength = 2 }); + }); + + options.AddOrUpdateProperty("Address", propertyInfo => + { + propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(255)); + }); + + options.AddOrUpdateProperty("Age", propertyInfo => + { + propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); + propertyInfo.ValidationAttributes.Add(new RangeAttribute(18, 99)); + }); + + options.AddOrUpdateProperty("IsMarried", propertyInfo => + { + + }); + }); + }); + } + + [Fact] + public void Should_Validate_If_The_Properties_Are_Valid() + { + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Name", "John"}, + {"Age", "42"}, + } + } + ).Count.ShouldBe(0); //All Valid + } + + [Fact] + public void Should_Not_Validate_If_The_Properties_Are_Not_Valid() + { + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject() + ).Count.ShouldBe(2); //Name & Age + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Address", new string('x', 256) } + } + } + ).Count.ShouldBe(3); //Name, Age & Address + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Age", "42" } + } + } + ).Count.ShouldBe(1); //Name + + ExtensibleObjectValidator + .GetValidationErrors( + new ExtensiblePersonObject + { + ExtraProperties = + { + {"Address", new string('x', 256) }, + {"Age", "100" } + } + } + ).Count.ShouldBe(3); //Name, Age & Address + } + + private class ExtensiblePersonObject : ExtensibleObject + { + + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs index cc80c24bc0..29006c151e 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs @@ -16,18 +16,22 @@ namespace Volo.Abp.TestApp.Testing city.HasProperty("UnknownProperty").ShouldBeFalse(); city.GetProperty("UnknownProperty").ShouldBeNull(); city.GetProperty("UnknownProperty").ShouldBe(0); + city.GetProperty("UnknownProperty", 42).ShouldBe(42); city.SetProperty("IsHot", true); city.HasProperty("IsHot").ShouldBeTrue(); city.GetProperty("IsHot").ShouldBeTrue(); + city.GetProperty("IsHot").ShouldBeTrue(); city.SetProperty("IsHot", false); city.HasProperty("IsHot").ShouldBeTrue(); city.GetProperty("IsHot").ShouldBeFalse(); + city.GetProperty("IsHot", true).ShouldBeFalse(); city.RemoveProperty("IsHot"); city.HasProperty("IsHot").ShouldBeFalse(); city.GetProperty("IsHot").ShouldBeFalse(); + city.GetProperty("IsHot", true).ShouldBeFalse(); } } } \ No newline at end of file From 2d35a8be962cff5b0dfbd8dbaae7fb50557fa749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 11 Apr 2020 16:24:21 +0300 Subject: [PATCH 3/7] Refactor ExtensibleObjectValidator --- .../ExtensibleObjectValidator.cs | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) 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 9b48798972..f29f334a8d 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs @@ -1,6 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using JetBrains.Annotations; using Volo.Abp.Data; using Volo.Abp.DynamicProxy; @@ -12,18 +12,14 @@ namespace Volo.Abp.ObjectExtending [NotNull] public static List GetValidationErrors( [NotNull] IHasExtraProperties extensibleObject, - [CanBeNull] IServiceProvider serviceProvider = null) + [CanBeNull] ValidationContext objectValidationContext = null) { var validationErrors = new List(); AddValidationErrors( extensibleObject, - new ValidationContext( - extensibleObject, - serviceProvider, - new Dictionary() - ), - validationErrors + validationErrors, + objectValidationContext ); return validationErrors; @@ -31,13 +27,21 @@ namespace Volo.Abp.ObjectExtending public static void AddValidationErrors( [NotNull] IHasExtraProperties extensibleObject, - [NotNull] ValidationContext validationContext, - [NotNull] List validationErrors) + [NotNull] List validationErrors, + [CanBeNull] ValidationContext objectValidationContext = null) { Check.NotNull(extensibleObject, nameof(extensibleObject)); - Check.NotNull(validationContext, nameof(validationContext)); Check.NotNull(validationErrors, nameof(validationErrors)); + if (objectValidationContext == null) + { + objectValidationContext = new ValidationContext( + extensibleObject, + null, + new Dictionary() + ); + } + var objectType = ProxyHelper.UnProxy(extensibleObject).GetType(); var objectExtensionInfo = ObjectExtensionManager.Instance @@ -50,23 +54,24 @@ namespace Volo.Abp.ObjectExtending foreach (var propertyInfo in objectExtensionInfo.GetProperties()) { - if (propertyInfo.ValidationAttributes.IsNullOrEmpty()) - { - continue; - } - - var validationContext2 = new ValidationContext(extensibleObject, validationContext, null) + if (propertyInfo.ValidationAttributes.Any()) { - DisplayName = propertyInfo.Name, - MemberName = propertyInfo.Name - }; + var propertyValidationContext = new ValidationContext(extensibleObject, objectValidationContext, null) + { + DisplayName = propertyInfo.Name, + MemberName = propertyInfo.Name + }; - foreach (var attribute in propertyInfo.ValidationAttributes) - { - var result = attribute.GetValidationResult(extensibleObject.GetProperty(propertyInfo.Name), validationContext2); - if (result != null) + foreach (var attribute in propertyInfo.ValidationAttributes) { - validationErrors.Add(result); + var result = attribute.GetValidationResult( + extensibleObject.GetProperty(propertyInfo.Name), + propertyValidationContext + ); + if (result != null) + { + validationErrors.Add(result); + } } } } 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 4/7] 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 From adfc60d8ca68ed7546b8b6267517e620f163db3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 11 Apr 2020 17:12:00 +0300 Subject: [PATCH 5/7] Add IsValid and refactor ExtensibleObjectValidator --- .../ExtensibleObjectValidator.cs | 119 +++++++++++++----- 1 file changed, 86 insertions(+), 33 deletions(-) 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 0c60f59699..07d6556d10 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs @@ -9,6 +9,17 @@ namespace Volo.Abp.ObjectExtending { public static class ExtensibleObjectValidator { + [NotNull] + public static bool IsValid( + [NotNull] IHasExtraProperties extensibleObject, + [CanBeNull] ValidationContext objectValidationContext = null) + { + return GetValidationErrors( + extensibleObject, + objectValidationContext + ).Any(); + } + [NotNull] public static List GetValidationErrors( [NotNull] IHasExtraProperties extensibleObject, @@ -81,45 +92,87 @@ namespace Volo.Abp.ObjectExtending foreach (var property in properties) { - if (property.ValidationAttributes.Any()) - { - var propertyValidationContext = new ValidationContext(extensibleObject, objectValidationContext, null) - { - DisplayName = property.Name, - MemberName = property.Name - }; - - foreach (var attribute in property.ValidationAttributes) - { - var result = attribute.GetValidationResult( - extensibleObject.GetProperty(property.Name), - propertyValidationContext - ); - if (result != null) - { - validationErrors.Add(result); - } - } - } + AddPropertyValidationErrors(extensibleObject, validationErrors, objectValidationContext, property); + } + } - if (property.Validators.Any()) + private static void AddPropertyValidationErrors( + IHasExtraProperties extensibleObject, + List validationErrors, + ValidationContext objectValidationContext, + ObjectExtensionPropertyInfo property) + { + AddPropertyValidationAttributeErrors( + extensibleObject, + validationErrors, + objectValidationContext, + property + ); + + ExecuteCustomPropertyValidationActions( + extensibleObject, + validationErrors, + objectValidationContext, + property + ); + } + + private static void AddPropertyValidationAttributeErrors( + IHasExtraProperties extensibleObject, + List validationErrors, + ValidationContext objectValidationContext, + ObjectExtensionPropertyInfo property) + { + if (!property.ValidationAttributes.Any()) + { + return; + } + + var propertyValidationContext = new ValidationContext(extensibleObject, objectValidationContext, null) + { + DisplayName = property.Name, + MemberName = property.Name + }; + + foreach (var attribute in property.ValidationAttributes) + { + var result = attribute.GetValidationResult( + extensibleObject.GetProperty(property.Name), + propertyValidationContext + ); + + if (result != null) { - var context = new ObjectExtensionPropertyValidationContext( - property, - extensibleObject, - validationErrors, - objectValidationContext, - extensibleObject.GetProperty(property.Name) - ); - - foreach (var validator in property.Validators) - { - validator(context); - } + validationErrors.Add(result); } } } + private static void ExecuteCustomPropertyValidationActions( + IHasExtraProperties extensibleObject, + List validationErrors, + ValidationContext objectValidationContext, + ObjectExtensionPropertyInfo property) + { + if (!property.Validators.Any()) + { + return; + } + + 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, From ec1b031a72c1cedeef5288494cd6b33775fa1a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 11 Apr 2020 17:17:19 +0300 Subject: [PATCH 6/7] Fix HasExtraPropertiesExtensions_Tests --- .../Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs index 29006c151e..918e96581b 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HasExtraPropertiesExtensions_Tests.cs @@ -31,7 +31,7 @@ namespace Volo.Abp.TestApp.Testing city.RemoveProperty("IsHot"); city.HasProperty("IsHot").ShouldBeFalse(); city.GetProperty("IsHot").ShouldBeFalse(); - city.GetProperty("IsHot", true).ShouldBeFalse(); + city.GetProperty("IsHot", true).ShouldBeTrue(); } } } \ No newline at end of file From d255b7e9a4fc7b88f3c0c7ace5d03ce4dd555283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 11 Apr 2020 19:37:26 +0300 Subject: [PATCH 7/7] Add Validation document for the object extensions. --- docs/en/Object-Extensions.md | 112 ++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 7 deletions(-) diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md index bbcb96374c..6eff328c85 100644 --- a/docs/en/Object-Extensions.md +++ b/docs/en/Object-Extensions.md @@ -156,11 +156,9 @@ ObjectExtensionManager.Instance ); ```` -#### Property Configuration +### Property Configuration -`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition. - -Example: +`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition: ````csharp ObjectExtensionManager.Instance @@ -168,13 +166,113 @@ ObjectExtensionManager.Instance "SocialSecurityNumber", options => { - options.CheckPairDefinitionOnMapping = false; + //Configure options... + }); +```` + +> `options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document. + +The following sections explain the fundamental property configuration options. + +#### CheckPairDefinitionOnMapping + +Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better. + +## Validation + +You may want to add some **validation rules** for the extra properties you've defined. `AddOrUpdateProperty` method options allows two ways of performing validation: + +1. You can add **data annotation attributes** for a property. +2. You can write an action (code block) to perform a **custom validation**. + +Validation works when you use the object in a method that is **automatically validated** (e.g. controller actions, page handler methods, application service methods...). So, all extra properties are validated whenever the extended object is being validated. + +### Data Annotation Attributes + +All of the standard data annotation attributes are valid for extra properties. Example: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.ValidationAttributes.Add(new RequiredAttribute()); + options.ValidationAttributes.Add( + new StringLengthAttribute(32) { + MinimumLength = 6 + } + ); + }); +```` + +With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided. + +### Custom Validation + +If you need, you can add a custom action that is executed to validate the extra properties. Example: + +````csharp +ObjectExtensionManager.Instance + .AddOrUpdateProperty( + "SocialSecurityNumber", + options => + { + options.Validators.Add(context => + { + var socialSecurityNumber = context.Value as string; + + if (socialSecurityNumber == null || + socialSecurityNumber.StartsWith("X")) + { + context.ValidationErrors.Add( + new ValidationResult( + "Invalid social security number: " + socialSecurityNumber, + new[] { "SocialSecurityNumber" } + ) + ); + } + }); }); ```` -> See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option. +`context.ServiceProvider` can be used to resolve a service dependency for advanced scenarios. + +In addition to add custom validation logic for a single property, you can add a custom validation logic that is executed in object level. Example: -`options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document. +````csharp +ObjectExtensionManager.Instance +.AddOrUpdate(objConfig => +{ + //Define two properties with their own validation rules + + objConfig.AddOrUpdateProperty("Password", propertyConfig => + { + propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); + }); + + objConfig.AddOrUpdateProperty("PasswordRepeat", propertyConfig => + { + propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); + }); + + //Write a common validation logic works on multiple properties + + objConfig.Validators.Add(context => + { + if (context.ValidatingObject.GetProperty("Password") != + context.ValidatingObject.GetProperty("PasswordRepeat")) + { + context.ValidationErrors.Add( + new ValidationResult( + "Please repeat the same password!", + new[] { "Password", "PasswordRepeat" } + ) + ); + } + }); +}); +```` ## Object to Object Mapping