Decorating a class with a ValidationAttribute

Jul 27, 2016 at 11:09 AM
Edited Jul 27, 2016 at 11:38 AM
Dear WCF Data Annotations developers / communit,

In WebAPI (and possibly in MVC, haven't checked), an attribute deriving from ValidationAttribute can be decorated on both property level and class level. In WCF Data Annotations, only property level is being inspected. I'm going to add such support for my own copy of WCF Data Annotations and was wondering if this is something you guys would like to add, or alternatively if you'd like me to make a pull request for :)

Granted, my use case is not extremely common, and the provided alternative is "close enough" - letting the class implement IValidatableObject. Still, this is somewhat less flexible, as it is not reusable. Consider the following code:
[AtLeastOnePersonHasABankAccountNumber]
public class GetLatestTransactionsReqeust {
  Person Applicant { get; set; }
  Person Partner { get; set; }
}

[AtLeastOnePersonHasABankAccountNumber]
public class GetDebtRequest {
  Person Applicant { get; set; }
  Person Partner { get; set; }
}
...and so forth for any other such request. When implementing IValidatableObject, at least a portion of the validation code would have to be duplicated. With the attribute in place, the code is clearer and more concise. Another point in favour of this: It may also be more intuitive for developers, as it seems that most of the documentations and discussions out there refer to attribute-based validation, and as we know, what we are used to is largely what we will be looking for.

Thoughts?

--- Edit: ---

The code adjustment I've come up with and seems to work is quite small. I've updated the method DataAnnotationsObjectValidator.GetValidationResults to be so:
// ***** Minor change in first argument name and XML documentation *****
    /// <summary>
    ///     Gets the validation results.
    /// </summary>
    /// <param name="type">The type to validate.</param>
    /// <param name="value">The value.</param>
    /// <returns></returns>
    private IEnumerable<ValidationResult> GetValidationResults(Type type, object value) {
      var enumerable = value as IEnumerable;

      if (enumerable != null) {
        foreach (var item in enumerable) {
          foreach (var result in Validate(item)) {
            yield return result;
          }
        }
      }

      // ***** New code start *****
      var context = new ValidationContext(value, null, null) {
        DisplayName = type.Name,
        MemberName = type.Name
      };

      foreach (var validationAttibute in type.GetCustomAttributes<ValidationAttribute>()) {
        var result = validationAttibute.GetValidationResult(value, context);

        if (result != ValidationResult.Success) {
          yield return result;
        }
      }
      // ***** New code end *****

      var properties = TypeDescriptor.GetProperties(type)
                                     .Cast<PropertyDescriptor>()
                                     .Where(p => !p.IsReadOnly);

      foreach (var property in properties) {
        foreach (var result in ValidateProperties(property, value)) {
          yield return result;
        }
      }
    }