mirror of https://github.com/abpframework/abp.git
committed by
GitHub
13 changed files with 647 additions and 13 deletions
@ -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<ValidationResult> GetValidationErrors( |
||||
|
[NotNull] IHasExtraProperties extensibleObject, |
||||
|
[CanBeNull] ValidationContext objectValidationContext = null) |
||||
|
{ |
||||
|
var validationErrors = new List<ValidationResult>(); |
||||
|
|
||||
|
AddValidationErrors( |
||||
|
extensibleObject, |
||||
|
validationErrors, |
||||
|
objectValidationContext |
||||
|
); |
||||
|
|
||||
|
return validationErrors; |
||||
|
} |
||||
|
|
||||
|
public static void AddValidationErrors( |
||||
|
[NotNull] IHasExtraProperties extensibleObject, |
||||
|
[NotNull] List<ValidationResult> 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<object, object>() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
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<ValidationResult> 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<ValidationResult> validationErrors, |
||||
|
ValidationContext objectValidationContext, |
||||
|
ObjectExtensionPropertyInfo property) |
||||
|
{ |
||||
|
AddPropertyValidationAttributeErrors( |
||||
|
extensibleObject, |
||||
|
validationErrors, |
||||
|
objectValidationContext, |
||||
|
property |
||||
|
); |
||||
|
|
||||
|
ExecuteCustomPropertyValidationActions( |
||||
|
extensibleObject, |
||||
|
validationErrors, |
||||
|
objectValidationContext, |
||||
|
property |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private static void AddPropertyValidationAttributeErrors( |
||||
|
IHasExtraProperties extensibleObject, |
||||
|
List<ValidationResult> 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<ValidationResult> 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<ValidationResult> 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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Related property extension information.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public ObjectExtensionPropertyInfo ExtensionPropertyInfo { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reference to the validating object.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public IHasExtraProperties ValidatingObject { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Add validation errors to this list.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public List<ValidationResult> ValidationErrors { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public ValidationContext ValidationContext { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The value of the validating property of the <see cref="ValidatingObject"/>.
|
||||
|
/// </summary>
|
||||
|
[CanBeNull] |
||||
|
public object Value { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can be used to resolve services from the dependency injection container.
|
||||
|
/// </summary>
|
||||
|
[CanBeNull] |
||||
|
public IServiceProvider ServiceProvider => ValidationContext; |
||||
|
|
||||
|
public ObjectExtensionPropertyValidationContext( |
||||
|
[NotNull] ObjectExtensionPropertyInfo objectExtensionPropertyInfo, |
||||
|
[NotNull] IHasExtraProperties validatingObject, |
||||
|
[NotNull] List<ValidationResult> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Related object extension information.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public ObjectExtensionInfo ObjectExtensionInfo { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reference to the validating object.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public IHasExtraProperties ValidatingObject { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Add validation errors to this list.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public List<ValidationResult> ValidationErrors { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Validation context comes from the <see cref="IValidatableObject.Validate"/> method.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public ValidationContext ValidationContext { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can be used to resolve services from the dependency injection container.
|
||||
|
/// </summary>
|
||||
|
[CanBeNull] |
||||
|
public IServiceProvider ServiceProvider => ValidationContext; |
||||
|
|
||||
|
public ObjectExtensionValidationContext( |
||||
|
[NotNull] ObjectExtensionInfo objectExtensionInfo, |
||||
|
[NotNull] IHasExtraProperties validatingObject, |
||||
|
[NotNull] List<ValidationResult> 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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<ExtensiblePersonObject>(options => |
||||
|
{ |
||||
|
options.AddOrUpdateProperty<string>("Name", propertyInfo => |
||||
|
{ |
||||
|
propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); |
||||
|
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(64) { MinimumLength = 2 }); |
||||
|
}); |
||||
|
|
||||
|
options.AddOrUpdateProperty<string>("Address", propertyInfo => |
||||
|
{ |
||||
|
propertyInfo.ValidationAttributes.Add(new StringLengthAttribute(255)); |
||||
|
}); |
||||
|
|
||||
|
options.AddOrUpdateProperty<byte>("Age", propertyInfo => |
||||
|
{ |
||||
|
propertyInfo.ValidationAttributes.Add(new RequiredAttribute()); |
||||
|
propertyInfo.ValidationAttributes.Add(new RangeAttribute(18, 99)); |
||||
|
}); |
||||
|
|
||||
|
options.AddOrUpdateProperty<bool>("IsMarried", propertyInfo => |
||||
|
{ |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
options.AddOrUpdateProperty<string>("Password", propertyInfo => |
||||
|
{ |
||||
|
}); |
||||
|
|
||||
|
options.AddOrUpdateProperty<string>("PasswordRepeat", propertyInfo => |
||||
|
{ |
||||
|
propertyInfo.Validators.Add(context => |
||||
|
{ |
||||
|
if (context.ValidatingObject.HasProperty("Password")) |
||||
|
{ |
||||
|
if (context.ValidatingObject.GetProperty<string>("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<string>("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 |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue