34

I'd like to use the data validation attributes in a library assembly, so that any consumer of the data can validate it without using a ModelBinder (in a console application, for instance). How can I do it?

Chris McCall
  • 10,317
  • 8
  • 49
  • 80

3 Answers3

55

Actually this is pretty cool. I used it in a WFP validation implementation recently. Most people end up writing lots of code using reflection to iterate the attributes, but there's a built in function for this.

var vc = new ValidationContext(myObject, null, null);
return Validator.TryValidateObject(myObject, vc, null, true);

You can also validate attributes on a single named property. You can also optionally pass in a list in order to access the error messages :

var results = new List<ValidationResult>();
var vc = new ValidationContext(myObject, null, null) { MemberName = "UserName"};
var isValid = Validator.TryValidateProperty(value, vc, results);

// get all the errors
var errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
mantal
  • 1,093
  • 1
  • 20
  • 38
TheCodeKing
  • 19,064
  • 3
  • 47
  • 70
  • I ended up writing a lot of reflection code to do this while I waited on an answer :( – Chris McCall Sep 24 '10 at 13:52
  • Yeah surprisingly few examples around using this API, and loads using reflection! – TheCodeKing Sep 24 '10 at 17:23
  • not a huge fan of output parameters, so I raised a custom exception instead (impossible to ignore) – Chris McCall Sep 26 '10 at 16:22
  • @TheCodeKing: most likely because there's loads of old examples for .NET 3.5 or older, where the number of validator attribute classes is really small and there's no ValidationContext nor Validation class. – Aoi Karasu Apr 05 '13 at 22:02
  • When initializing the VaildationContext why is it required to also specify the MemberName property? I found I kept getting ArgumentNullException when not specifying it. – nityan Jun 24 '14 at 00:23
  • 3
    This is fantastic! For those interested, I use that in [this gist](https://gist.github.com/JimmyBoh/b7c135820c18a06648a5) as a simple helper class. – Jim Buck Aug 11 '14 at 18:03
  • Very Handy. I just added a few lines to my Business Object base class and I can use across all of my BO. Thanks Much! – pStan Mar 10 '15 at 14:18
  • 7
    So i finally implemented this the right way this morning, 5 years after asking. Thanks man – Chris McCall Jun 03 '15 at 17:45
  • 2
    What about nested objects / lists of objects / etc? This solution is non-recursive, but I think to be fully robust, it would need to be recursive. Anything built-in for that, or would you have to roll that manually? – gzak Aug 03 '17 at 01:34
  • 2
    @gzak For nested items I built my own ValidateObjectAttribute that inherits from ValidationAttribute. See this gist https://gist.github.com/odyth/905bcaa84d257f663eb45a55481b660b – odyth Aug 21 '18 at 19:31
2

The System.ComponentModel.DataAnnotations.ValidationAttribute classes have IsValid methods that perform the validation logic. They take an Object (the value of the field they decorate) and return true or false.

You can use these attributes, and a little reflection, to roll your own aspect-oriented validator. Pass your validator an object, and the validator will get a list of PropertyInfos and FieldInfos. For each of these, it can call GetCustomAttributes to look for those that inherit from ValidationAttribute, and for each of these, call IsValid, passing the value of the property or field. This can be done totally dynamically without knowing the structure of the class to be validated at design-time.

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
KeithS
  • 70,210
  • 21
  • 112
  • 164
0

TryValidateProperty is just badly written - you have to jump through hoops to get it to work outside a Controller, and even then, if you use it twice, it will end up quietly setting ModelState to valid/invalid and stop alterring that state, and stop returning accurate results from then on.

I gave up on it and just wrote my own validator. This'll loop over any set of objects in any context and tell you if they're valid:

bool isValid = true;
var invalidFields = new List<string>();

foreach (var o in viewModels)
{
    var properties = o.GetType()
        .GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach(var prop in properties)
    {
        var attrs = prop.GetCustomAttributes(true);
        if (attrs != null)
        {
            var val = prop.GetValue(o);
            ValidationAttribute[] validatorAttrs = attrs
                .Where(a => a is ValidationAttribute)
                .Select(a => (ValidationAttribute)a).ToArray();

            foreach(var attr in validatorAttrs)
            {                       
                bool thisFieldIsValid = attr.IsValid(val);
                if (!thisFieldIsValid)
                    invalidFields.Add(prop.Name);

                isValid = isValid && thisFieldIsValid;
            }
        }
    }
}
Chris Moschini
  • 36,764
  • 19
  • 160
  • 190