C# DataAnnotations:递归验证整个对象图
我有一个对象图,上面点缀着DataAnnotation属性,其中对象的一些属性是类,它们本身具有验证属性,等等 在以下场景中:C# DataAnnotations:递归验证整个对象图,c#,.net,validation,recursion,data-annotations,C#,.net,Validation,Recursion,Data Annotations,我有一个对象图,上面点缀着DataAnnotation属性,其中对象的一些属性是类,它们本身具有验证属性,等等 在以下场景中: public class Employee { [Required] public string Name { get; set; } [Required] public Address Address { get; set; } } public class Address { [Required] public s
public class Employee
{
[Required]
public string Name { get; set; }
[Required]
public Address Address { get; set; }
}
public class Address
{
[Required]
public string Line1 { get; set; }
public string Line2 { get; set; }
[Required]
public string Town { get; set; }
[Required]
public string PostalCode { get; set; }
}
如果我尝试验证一个员工的地址,但没有PostalCode
的值,那么我希望(并且期望)出现异常,但我没有得到任何异常。我是这样做的:
var employee = new Employee
{
Name = "Neil Barnwell",
Address = new Address
{
Line1 = "My Road",
Town = "My Town",
PostalCode = "" // <- INVALID!
}
};
Validator.ValidateObject(employee, new ValidationContext(employee, null, null));
var employee=新员工
{
Name=“尼尔·巴恩韦尔”,
地址=新地址
{
Line1=“我的路”,
Town=“我的城镇”,
PostalCode=“”//这里有一种替代选择加入属性方法的方法。我相信这将正确地遍历对象图并验证所有内容
public bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results) {
bool result = TryValidateObject(obj, results);
var properties = obj.GetType().GetProperties().Where(prop => prop.CanRead
&& !prop.GetCustomAttributes(typeof(SkipRecursiveValidation), false).Any()
&& prop.GetIndexParameters().Length == 0).ToList();
foreach (var property in properties)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) continue;
var value = obj.GetPropertyValue(property.Name);
if (value == null) continue;
var asEnumerable = value as IEnumerable;
if (asEnumerable != null)
{
foreach (var enumObj in asEnumerable)
{
var nestedResults = new List<ValidationResult>();
if (!TryValidateObjectRecursive(enumObj, nestedResults))
{
result = false;
foreach (var validationResult in nestedResults)
{
PropertyInfo property1 = property;
results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
}
};
}
}
else
{
var nestedResults = new List<ValidationResult>();
if (!TryValidateObjectRecursive(value, nestedResults))
{
result = false;
foreach (var validationResult in nestedResults)
{
PropertyInfo property1 = property;
results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
}
}
}
}
return result;
}
public bool TryValidateObjectRecursive(T对象,列出结果){
bool result=TryValidateObject(obj,results);
var properties=obj.GetType().GetProperties().Where(prop=>prop.CanRead
&&!prop.GetCustomAttributes(typeof(SkipRecursiveValidation),false).Any()
&&prop.GetIndexParameters().Length==0.ToList();
foreach(属性中的var属性)
{
如果(property.PropertyType==typeof(string)| | property.PropertyType.IsValueType)继续;
var value=obj.GetPropertyValue(property.Name);
如果(值==null)继续;
var asEnumerable=作为IEnumerable的值;
如果(asEnumerable!=null)
{
foreach(变量enumObj在可计算中)
{
var nestedResults=新列表();
如果(!TryValidateObjectRecursive(enumObj,nestedResults))
{
结果=假;
foreach(nestedResults中的var validationResult)
{
PropertyInfo property1=属性;
添加(新的ValidationResult(ValidationResult.ErrorMessage,ValidationResult.MemberNames.Select(x=>property1.Name+'.+x));
}
};
}
}
其他的
{
var nestedResults=新列表();
如果(!TryValidateObjectRecursive(值,nestedResults))
{
结果=假;
foreach(nestedResults中的var validationResult)
{
PropertyInfo property1=属性;
添加(新的ValidationResult(ValidationResult.ErrorMessage,ValidationResult.MemberNames.Select(x=>property1.Name+'.+x));
}
}
}
}
返回结果;
}
最新代码:
包装:
此外,我还更新了此解决方案以处理循环对象图。感谢您的反馈。代码:
public class DataAnnotationsValidator : IDataAnnotationsValidator
{
public bool TryValidateObject(object obj, ICollection<ValidationResult> results, IDictionary<object, object> validationContextItems = null)
{
return Validator.TryValidateObject(obj, new ValidationContext(obj, null, validationContextItems), results, true);
}
public bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results, IDictionary<object, object> validationContextItems = null)
{
return TryValidateObjectRecursive(obj, results, new HashSet<object>(), validationContextItems);
}
private bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results, ISet<object> validatedObjects, IDictionary<object, object> validationContextItems = null)
{
//short-circuit to avoid infinite loops on cyclic object graphs
if (validatedObjects.Contains(obj))
{
return true;
}
validatedObjects.Add(obj);
bool result = TryValidateObject(obj, results, validationContextItems);
var properties = obj.GetType().GetProperties().Where(prop => prop.CanRead
&& !prop.GetCustomAttributes(typeof(SkipRecursiveValidation), false).Any()
&& prop.GetIndexParameters().Length == 0).ToList();
foreach (var property in properties)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) continue;
var value = obj.GetPropertyValue(property.Name);
if (value == null) continue;
var asEnumerable = value as IEnumerable;
if (asEnumerable != null)
{
foreach (var enumObj in asEnumerable)
{
if ( enumObj != null) {
var nestedResults = new List<ValidationResult>();
if (!TryValidateObjectRecursive(enumObj, nestedResults, validatedObjects, validationContextItems))
{
result = false;
foreach (var validationResult in nestedResults)
{
PropertyInfo property1 = property;
results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
}
};
}
}
}
else
{
var nestedResults = new List<ValidationResult>();
if (!TryValidateObjectRecursive(value, nestedResults, validatedObjects, validationContextItems))
{
result = false;
foreach (var validationResult in nestedResults)
{
PropertyInfo property1 = property;
results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
}
};
}
}
return result;
}
}
public class ValidateObjectAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(value, null, null);
Validator.TryValidateObject(value, context, results, true);
if (results.Count != 0)
{
var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
}
public class ValidateCollectionAttribute : ValidationAttribute
{
public Type ValidationType { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var collectionResults = new CompositeValidationResult(String.Format("Validation for {0} failed!",
validationContext.DisplayName));
var enumerable = value as IEnumerable;
var validators = GetValidators().ToList();
if (enumerable != null)
{
var index = 0;
foreach (var val in enumerable)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(val, validationContext.ServiceContainer, null);
if (ValidationType != null)
{
Validator.TryValidateValue(val, context, results, validators);
}
else
{
Validator.TryValidateObject(val, context, results, true);
}
if (results.Count != 0)
{
var compositeResults =
new CompositeValidationResult(String.Format("Validation for {0}[{1}] failed!",
validationContext.DisplayName, index));
results.ForEach(compositeResults.AddResult);
collectionResults.AddResult(compositeResults);
}
index++;
}
}
if (collectionResults.Results.Any())
{
return collectionResults;
}
return ValidationResult.Success;
}
private IEnumerable<ValidationAttribute> GetValidators()
{
if (ValidationType == null) yield break;
yield return (ValidationAttribute)Activator.CreateInstance(ValidationType);
}
}
public class CompositeValidationResult : ValidationResult
{
private readonly List<ValidationResult> _results = new List<ValidationResult>();
public IEnumerable<ValidationResult> Results
{
get
{
return _results;
}
}
public CompositeValidationResult(string errorMessage) : base(errorMessage) { }
public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) { }
protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) { }
public void AddResult(ValidationResult validationResult)
{
_results.Add(validationResult);
}
}
public interface IDataAnnotationsValidator
{
bool TryValidateObject(object obj, ICollection<ValidationResult> results, IDictionary<object, object> validationContextItems = null);
bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results, IDictionary<object, object> validationContextItems = null);
}
public static class ObjectExtensions
{
public static object GetPropertyValue(this object o, string propertyName)
{
object objValue = string.Empty;
var propertyInfo = o.GetType().GetProperty(propertyName);
if (propertyInfo != null)
objValue = propertyInfo.GetValue(o, null);
return objValue;
}
}
public class SkipRecursiveValidation : Attribute
{
}
public class SaveValidationContextAttribute : ValidationAttribute
{
public static IList<ValidationContext> SavedContexts = new List<ValidationContext>();
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
SavedContexts.Add(validationContext);
return ValidationResult.Success;
}
}
公共类DataAnnotationsValidator:IDataAnnotationsValidator
{
public bool TryValidateObject(对象对象对象,ICollection结果,IDictionary validationContextItems=null)
{
返回Validator.TryValidateObject(obj,新的ValidationContext(obj,null,validationContextItems),结果,true);
}
public bool TryValidateObjectRecursive(T obj,列表结果,IDictionary validationContextItems=null)
{
返回TryValidateObjectRecursive(obj,results,new HashSet(),validationContextItems);
}
私有bool TryValidateObjectRecursive(T对象,列表结果,ISet验证对象,IDictionary验证上下文项=null)
{
//循环对象图上避免无限循环的短路
if(validatedObjects.Contains(obj))
{
返回true;
}
validatedObjects.Add(obj);
bool result=TryValidateObject(obj、results、validationContextItems);
var properties=obj.GetType().GetProperties().Where(prop=>prop.CanRead
&&!prop.GetCustomAttributes(typeof(SkipRecursiveValidation),false).Any()
&&prop.GetIndexParameters().Length==0.ToList();
foreach(属性中的var属性)
{
如果(property.PropertyType==typeof(string)| | property.PropertyType.IsValueType)继续;
var value=obj.GetPropertyValue(property.Name);
如果(值==null)继续;
var asEnumerable=作为IEnumerable的值;
如果(asEnumerable!=null)
{
foreach(变量enumObj在可计算中)
{
if(enumObj!=null){
var nestedResults=新列表();
如果(!TryValidateObjectRecursive(enumObj、nestedResults、ValidatedObject、validationContextItems))
{
结果=假;
foreach(nestedResults中的var validationResult)
{
PropertyInfo property1=属性;
添加(新的ValidationResult(ValidationResult.ErrorMessage,ValidationResult.MemberNames.Select(x=>property1.Name+'.+x));
}
};
}
}
}
其他的
{
var nestedResults=新列表();
如果(!TryValidateObjectRecursive(值、nestedResults、ValidatedObject、validationContextItems))
{
结果=假;
foreach(nestedResults中的var validationResult)
{
PropertyInfo property1=属性;
添加(新的ValidationResult(ValidationResult.ErrorMessage,ValidationResult.MemberNames.Select(x=>property1.Name+'.+x));
}
};
}
}
返回结果;
}
}
公共类ValidateObject属性:ValidationAttribute
{
受保护的重写ValidationResult有效(对象值,ValidationContext ValidationContext)
{
var results=新列表();
var context=newvalidationcontext(值,null,null);
TryValidateObject(值、上下文、结果、true);
如果(results.Count!=0)
{
/// <summary>
/// Validates given <paramref name="obj"/>
/// </summary>
/// <param name="obj"></param>
/// <param name="items">optional items</param>
/// <param name="validationResults">optional list of <see cref="ValidationResult"/></param>
public bool TryValidateObject(object obj, Dictionary<object, object> items, List<ValidationResult> validationResults)
{
// create validation context
ValidationContext validationContext = new ValidationContext(obj, this.serviceProvider, items);
// do validation
if (validationResults == null)
validationResults = new List<ValidationResult>();
bool result = true;
if (!Validator.TryValidateObject(obj, validationContext, validationResults, true))
result = false;
// do validation of nested objects
if (obj == null)
return result;
// get properties that can be validated
List<PropertyInfo> properties = obj.GetType()
.GetProperties()
.Where(prop => prop.CanRead && prop.GetIndexParameters().Length == 0)
.Where(prop => CanTypeBeValidated(prop.PropertyType))
.ToList();
// loop over each property
foreach (PropertyInfo property in properties)
{
// get and check value
var value = property.GetValue(obj);
if (value == null)
continue;
// check whether its an enumerable - if not, put the value in a new enumerable
IEnumerable<object> valueEnumerable = value as IEnumerable<object>;
if (valueEnumerable == null)
{
valueEnumerable = new object[] { value };
}
// validate values in enumerable
foreach (var valueToValidate in valueEnumerable)
{
List<ValidationResult> nestedValidationResults = new List<ValidationResult>();
if (!TryValidateObject(valueToValidate, items, nestedValidationResults))
{
result = false;
// add nested results to this results (so the member names are correct)
foreach (var validationResult in nestedValidationResults)
{
validationResults.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property.Name + '.' + x)));
}
}
}
}
return result;
}
/// <summary>
/// Returns whether the given <paramref name="type"/> can be validated
/// </summary>
private bool CanTypeBeValidated(Type type)
{
if (type == null)
return false;
if (type == typeof(string))
return false;
if (type.IsValueType)
return false;
if (type.IsArray && type.HasElementType)
{
Type elementType = type.GetElementType();
return CanTypeBeValidated(elementType);
}
return true;
}
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
</ItemGroup>
public class Employee
{
[Required]
public string Name { get; set; }
[ValidateComplexType]
public Address Address { get; set; }
}
public class Address
{
[Required]
public string Line1 { get; set; }
public string Line2 { get; set; }
[Required]
public string Town { get; set; }
[Required]
public string PostalCode { get; set; }
}
<ObjectGraphDataAnnotationsValidator />