Determine if a property is a complex object using

2019-07-21 00:39发布

问题:

I'm trying to do some validation in a WCF service, and for that I'm using WCFDataAnnotations which I found through this post

Problem is that it doesn't validate recursively, so for a nested object it doesn't work. Let's say this

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

}

It won't get the childModel RequiredValue as precisely that, required.

So I was taking a look into the source code of that dll and trying to make it work. The actual code is

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

So my thought are changing this to something like this

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

The intention is check if any of the properties is a complex one and if it's the case validate itself recursively, but I'm not sure how to check that given the "props" are casted to TypeDescriptors.

Thanks

回答1:

Seems to me that the following code should do the trick:

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

Might not be the most elegant solution, but i think it'll work.



回答2:

Now I am able to validate public List ChildModel. With Reference to DevTrends.WCFDataAnnotations, ValidatingParameterInspector.cs class, (http://wcfdataannotations.codeplex.com/SourceControl/latest#DevTrends.WCFDataAnnotations/ValidatingParameterInspector.cs).

I am sure ValidateCollection can be further modified to check collection within ChildModel.Currently it check upto one level only .

My Example,

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

}

Original code do not validate List,So I created one more function ValidateCollection Manipulated the object[] inputs to extract each ChildModel class and put it back in object[] inputs like model class reside in object[] inputs.

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

        }