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