使用PropertyDescriptor确定属性是否是复杂对象

时间:2014-08-19 16:39:48

标签: c# wcf reflection data-annotations

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

问题是它不会递归验证,因此对于嵌套对象,它不起作用。让我们说这个

[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。

由于

2 个答案:

答案 0 :(得分: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;
}

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

答案 1 :(得分:0)

现在我能够验证公共List 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;
            }

        }