DataAnnotations:递归验证整个对象图

时间:2011-10-05 15:07:50

标签: 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 string Line1 { get; set; }

    public string Line2 { get; set; }

    [Required]
    public string Town { get; set; }

    [Required]
    public string PostalCode { get; set; }
}

如果我尝试验证Employee的{​​{1}}没有Address的值,那么我希望(并期望)一个例外,但我没有。以下是我的表现:

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)); 还有哪些其他选项可以确保所有属性都经过递归验证

非常感谢提前。

6 个答案:

答案 0 :(得分:52)

我的回答太久了,所以我把它变成了博客文章:)

Recursive Validation Using DataAnnotations

该解决方案为您提供了一种使用您现在使用的相同基本方法实现递归验证的方法。

答案 1 :(得分:31)

这是选择加入属性方法的替代方案。我相信这将正确地遍历对象图并验证一切。

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;
}

最新代码: https://github.com/reustmd/DataAnnotationsValidatorRecursive

包装: https://www.nuget.org/packages/DataAnnotationsValidator/

此外,我已更新此解决方案以处理循环对象图。感谢您的反馈。

答案 2 :(得分:1)

我在寻找Blazor遇到的类似问题时发现了这个问题。看到Blazor变得越来越流行,我认为这是一个提起我如何解决此问题的好地方。

首先,使用软件包管理器控制台安装以下软件包:Install-Package Microsoft.AspNetCore.Components.DataAnnotations.Validation -Version 3.2.0-rc1.20223.4

或者,您也可以手动将其添加到.csproj文件中:

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
</ItemGroup>

添加并安装了此软件包,您可以仅将以下数据注释添加到任何对象以表明它是复杂类型。使用提供的示例OP:

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; }
}

请注意[ValidateComplexType]参考上方的Address注释。

对于使用Blazor时也发现此帖子的人: 确保您的EditForm使用此AnnotationValidator而不是普通的:

<ObjectGraphDataAnnotationsValidator />

来源:https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-3.1#blazor-data-annotations-validation-package

答案 3 :(得分:1)

您可以扩展默认验证行为,使您要验证的类实现 IValidatableObject 接口

public class Employee : IValidatableObject
{
    [Required]
    public string Name { get; set; }

    [Required]
    public Address Address { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();

        Validator.TryValidateObject(Address, new ValidationContext(Address), results, validateAllProperties: true);

        return results;
    }
}

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; }
}

并通过以下方式之一使用 Validator 类对其进行验证

Validator.ValidateObject(employee, new ValidationContext(employee), validateAllProperties: true);

var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(employee, new ValidationContext(employee), validationResults, validateAllProperties: true);

答案 4 :(得分:0)

我从j_freyre清除了一些代码。如果您没有“ this.serviceProvider”,则可以将其替换为“ null”。

    /// <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;
    }

答案 5 :(得分:-1)

代码:

ByteFormatPipe