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