【问题标题】:Determine if a property is a complex object using PropertyDescriptor使用 PropertyDescriptor 确定属性是否为复杂对象
【发布时间】:2014-10-12 20:46:23
【问题描述】:

我正在尝试在 WCF 服务中进行一些验证,为此我正在使用通过 this post 找到的 WCFDataAnnotations

问题是它不递归验证,所以对于嵌套对象它不起作用。这么说吧

[DataContract]
public class Model
{
    [DataMember]
    [Required(ErrorMessage = "RequiredOne is required")]
    public string RequiredOne { get; set; }

    [DataMember]
    [StringLength(10, ErrorMessage = "Not Required should be at most 10 characters long")]
    public string NotRequired { get; set; }

    [DataMember]
    [Required(ErrorMessage = "ChildModel is required")]
    public ChildModel ChildModel { get; set; }

}

[DataContract]
public class ChildModel
{
    [DataMember]
    [Required(ErrorMessage = "RequiredValue is required")]
    public string RequiredValue { get; set; } 

    [DataMember]
    public string NotRequiredValue { get; set; }

}

它不会准确地获得所需的 childModel RequiredValue。

所以我查看了那个 dll 的源代码并试图让它工作。实际代码是

public class DataAnnotationsObjectValidator : IObjectValidator
{
    public IEnumerable<ValidationResult> Validate(object input)
    {
        if (input == null) return Enumerable.Empty<ValidationResult>();

        return from property in TypeDescriptor.GetProperties(input).Cast<PropertyDescriptor>()
               from attribute in property.Attributes.OfType<ValidationAttribute>()
               where !attribute.IsValid(property.GetValue(input))
               select new ValidationResult
               (
                   attribute.FormatErrorMessage(string.Empty),
                   new[] { property.Name }
               );
    }
} 

所以我的想法是把它改成这样

public IEnumerable<ValidationResult> Validate(object input)
{
    if (input == null) return Enumerable.Empty<ValidationResult>();

    var validationResults = new List<ValidationResult>();

    foreach (var prop in TypeDescriptor.GetProperties(input).Cast<PropertyDescriptor>())
    {
        foreach (var att in prop.Attributes.OfType<ValidationAttribute>())
        {
            //This doesn't work, it's one of the several 
            //attempts I've made
            if (prop.ComponentType.IsClass)
                Validate(prop.ComponentType);

            if (!att.IsValid(prop.GetValue(input)))
            {
                validationResults.Add(new ValidationResult(
                        att.FormatErrorMessage(string.Empty),
                        new[] { prop.Name }
                ));
            }
        }
    }

    return validationResults;
}

目的是检查是否有任何属性是复杂的,如果是这种情况,则递归验证自身,但我不确定如何检查给定“props”是否被强制转换为 TypeDescriptors。

谢谢

【问题讨论】:

    标签: c# wcf reflection data-annotations


    【解决方案1】:

    在我看来,下面的代码应该可以解决问题:

    public IEnumerable<ValidationResult> Validate(object input)
    {
        return ValidateWithState(input, new HashSet<object>());
    }
    
    private IEnumerable<ValidationResult> ValidateWithState(object input, HashSet<object> traversedInputs)
    {
        if (input == null || traversedInputs.Contains(input))
        {
           return Enumerable.Empty<ValidationResult>();
        }
    
        var validationResults = new List<ValidationResult>();
    
        foreach (var prop in TypeDescriptor.GetProperties(input).Cast<PropertyDescriptor>())
        {
            foreach (var att in prop.Attributes.OfType<ValidationAttribute>())
            {
                if (!att.IsValid(prop.GetValue(input)))
                {
                    validationResults.Add(new ValidationResult(
                            att.FormatErrorMessage(string.Empty),
                            new[] { prop.Name }
                ));
            }
    
            traversedInputs.Add(input);
    
            if (prop.PropertyType.IsClass || prop.PropertyType.IsInterface))
            {
                validationResults.AddRange(ValidateWithState(prop.GetValue(input), traversedInputs));
            }
        }
    
        return validationResults;
    }
    

    可能不是最优雅的解决方案,但我认为它会起作用。

    【讨论】:

    • 非常感谢@Roman,我已经更新了它们在包本身中的测试(我的意思是在本地),它似乎工作正常,所以我想知道是否值得以某种方式通知业主以防他们想包含它
    • 我刚刚意识到,我发布的代码不处理循环引用。向 DataAnnotationsObjectValidator 添加一个状态应该不会太难,它将保持计数并忽略以前验证的对象。
    • 我正在尝试添加该状态,但不确定是否像我预期的那样容易
    • 如何识别是否有任何集合或通用集合类型?在上面的例子中说它是 public ChildModel List { get;放; }
    【解决方案2】:

    现在我可以验证公共列表 ChildModel。 参考 DevTrends.WCFDataAnnotations,ValidatingParameterInspector.cs 类, (http://wcfdataannotations.codeplex.com/SourceControl/latest#DevTrends.WCFDataAnnotations/ValidatingParameterInspector.cs)。

    我确信 ValidateCollection 可以进一步修改以检查 ChildModel 中的集合。目前它只检查一个级别。

    我的例子,

    [DataContract]
    public class Model
    {
        [DataMember]
        [Required(ErrorMessage = "RequiredOne is required")]
        public string RequiredOne { get; set; }
    
        [DataMember]
        [StringLength(10, ErrorMessage = "Not Required should be at most 10 characters long")]
        public string NotRequired { get; set; }
    
        [DataMember]
        [Required(ErrorMessage = "ChildModel is required")]
        public List<ChildModel> ChildModel { get; set; }
    
    }
    

    原代码不验证List,所以我又创建了一个函数ValidateCollection 操纵 object[] 输入以提取每个 ChildModel 类并将其放回 object[] 输入中,就像模型类驻留在 object[] 输入中一样。

    public object BeforeCall(string operationName, object[] inputs)
                  {
                    var validationResults = new List<ValidationResult>();               ErrorMessageGenerator.isValidationFail = false;
                    ErrorMessageGenerator.ErrorMessage = string.Empty;
                    ***inputs=ValidateCollection( operationName, inputs);***
                    foreach (var input in inputs)
        {
                       foreach (var validator in _validators)
                        {
                            var results = validator.Validate(input);
                            validationResults.AddRange(results);
                        }
                   }
                          if (validationResults.Count > 0)
                    {
                      return _errorMessageGenerator.GenerateErrorMessage(operationName, validationResults);
                    }
                    return null;
                }
    
      private object[] ValidateCollection(string operationName, object[] inputs)
            {
                object[] inputs1 = inputs;
                try
                {
                    foreach (var input in inputs)
                    {
                        foreach (var property in input.GetType().GetProperties())
                        {
                            IEnumerable enumerable = null;
                            if (property.PropertyType.Name.Contains("List"))
                            {
                                enumerable = property.GetValue(input, null) as IEnumerable;
                                int j = 0;
              object[] o1 = new object[inputs.Count() + enumerable.OfType<object>().Count()];
                                for (int k = 0; k < inputs.Count(); k++)
                                {
                                    o1[k] = inputs[k];
                                }
                                foreach (var item in enumerable)
                                {
    
                                    o1[inputs.Count() + j] = item;
                                    j = j + 1;
                                    if (j == (o1.Length - inputs.Count()))
                                        inputs = o1;
                                }
                            }
    
                        }
                    }
                    return inputs;
                }
                catch
                {
                    return inputs1;
                }
    
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多