DataAnnotations:递归验证整个对象图

发布于 2024-12-08 13:57:54 字数 949 浏览 1 评论 0原文

我有一个对象图,上面散布着 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; }
}

如果我尝试验证没有 PostalCode 值的 EmployeeAddress,那么我希望(并期望)一个例外,但我没有得到。我的做法如下:

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

Validator 还有哪些其他选项可以确保所有属性都得到递归验证?

I have an object graph sprinkled with DataAnnotation attributes, where some properties of objects are classes which themselves have validation attributes, and so on.

In the following scenario:

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

If I try to validate an Employee's Address with no value for PostalCode, then I would like (and expect) an exception, but I get none. Here's how I'm doing it:

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

What other options do I have with Validator that would ensure all properties are validated recursively?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

鸠书 2024-12-15 13:57:54

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

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/

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

Here's an alternative to the opt-in attribute approach. I believe this will traverse the object-graph properly and validate everything.

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

Most up-to-date code:
https://github.com/reustmd/DataAnnotationsValidatorRecursive

Package:
https://www.nuget.org/packages/DataAnnotationsValidator/

Also, I have updated this solution to handle cyclical object graphs. Thanks for the feedback.

以可爱出名 2024-12-15 13:57:54

您可以扩展默认验证行为,使要验证的类实现 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);

You can extend the default validation behavior, making the class you want to validate implement the IValidatableObject interface

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

And validate it using the Validator class in one of these ways

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

or

var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(employee, new ValidationContext(employee), validationResults, validateAllProperties: true);
情丝乱 2024-12-15 13:57:54

我在搜索 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; }
}

记下 Address 引用上方的 [ValidateComplexType] 注释。

对于在使用 Blazor 时也发现这篇文章的人:
确保您的 EditForm 使用此 AnnotationValidator 而不是普通的 AnnotationValidator:

<ObjectGraphDataAnnotationsValidator />

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

I found this issue while searching for a similar problem I had with Blazor. Seeing as Blazor is becoming increasingly more popular I figured this would be a good place to mention how I solved this problem.

Firstly, install the following package using your package manager console: Install-Package Microsoft.AspNetCore.Components.DataAnnotations.Validation -Version 3.2.0-rc1.20223.4

Alternatively you can also add it manually in your .csproj file:

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

Having added and installed this package one can simply add the following data annotation to any object to indicate that it is a complex type. Using the example OP provided:

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

Take note of the [ValidateComplexType] annotation above the Address reference.

For the ones that also found this post when using Blazor:
make sure your EditForm uses this AnnotationValidator instead of the normal one:

<ObjectGraphDataAnnotationsValidator />

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

站稳脚跟 2024-12-15 13:57:54

MiniValidation 库可能是最好的最新解决方案。

验证将是一行:

var isValid = MiniValidator.TryValidate(employee, out var errors);

MiniValidation library is probably the best up to date solution.

The validation will be one liner:

var isValid = MiniValidator.TryValidate(employee, out var errors);
萧瑟寒风 2024-12-15 13:57:54

代码:

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

Code:

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;
    }
}
晚雾 2024-12-15 13:57:54

我稍微清理了 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;
    }

I cleaned the code from j_freyre a little. The "this.serviceProvider" can be replaced with "null" if you dont have one.

    /// <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;
    }
抚你发端 2024-12-15 13:57:54

Fluent Validation 库为验证复杂嵌套对象提供了出色的支持。因此,假设在您的应用程序中您有客户,并且客户可以拥有订单集合,如下所示:

public class Customer 
{
  public List<Order> Orders { get; set; } = new List<Order>();
}

public class Order 
{
  public double Total { get; set; }
}

现在,要验证客户,您所需要做的就是为每个订单设置订单验证器:

public class OrderValidator : AbstractValidator<Order> 
{
  public OrderValidator() 
  {
    RuleFor(x => x.Total).GreaterThan(0);
  }
}

public class CustomerValidator : AbstractValidator<Customer> 
{
  public CustomerValidator() 
  {
    RuleForEach(x => x.Orders).SetValidator(new OrderValidator());
  }
}

Fluent Validation library provides excellent support for validating complex nested objects. So, say in your application you have Customer's, and Customer's can have collections of Orders, like this:

public class Customer 
{
  public List<Order> Orders { get; set; } = new List<Order>();
}

public class Order 
{
  public double Total { get; set; }
}

Now, to validate Customer's, all you need is to set orders validator for each Order:

public class OrderValidator : AbstractValidator<Order> 
{
  public OrderValidator() 
  {
    RuleFor(x => x.Total).GreaterThan(0);
  }
}

public class CustomerValidator : AbstractValidator<Customer> 
{
  public CustomerValidator() 
  {
    RuleForEach(x => x.Orders).SetValidator(new OrderValidator());
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文