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 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..07d6556d10 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectValidator.cs @@ -0,0 +1,200 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using JetBrains.Annotations; +using Volo.Abp.Data; +using Volo.Abp.DynamicProxy; + +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, + [CanBeNull] ValidationContext objectValidationContext = null) + { + var validationErrors = new List(); + + AddValidationErrors( + extensibleObject, + validationErrors, + objectValidationContext + ); + + return validationErrors; + } + + public static void AddValidationErrors( + [NotNull] IHasExtraProperties extensibleObject, + [NotNull] List validationErrors, + [CanBeNull] ValidationContext objectValidationContext = null) + { + Check.NotNull(extensibleObject, nameof(extensibleObject)); + 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 + .GetOrNull(objectType); + + if (objectExtensionInfo == null) + { + return; + } + + 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) + { + AddPropertyValidationErrors(extensibleObject, validationErrors, objectValidationContext, property); + } + } + + 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) + { + 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, + 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); + } + } + } +} \ No newline at end of file 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 2edae9a3f8..e6dbbfd6ce 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,12 @@ namespace Volo.Abp.ObjectExtending [NotNull] public Type Type { get; } + [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; @@ -42,6 +49,8 @@ namespace Volo.Abp.ObjectExtending Name = Check.NotNull(name, nameof(name)); 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/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 => 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..8c27844e22 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ExtensibleObjectValidator_Tests.cs @@ -0,0 +1,172 @@ +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 => + { + + }); + + 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" } + ) + ); + } + }); + }); + }); + } + + [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 + + 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 + { + + } + } +} 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..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 @@ -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).ShouldBeTrue(); } } } \ No newline at end of file