You can use the Validator utility class to do manual attribute-based validation on any object, in any project type (as opposed to doing automatic model validation in ASP.NET). To do this, add model validation attributes to your class properties, then create an object and populate it (aka binding), and finally execute manual validation with the Validator class.
Here’s an example. First, the class needs model validation attributes (from System.ComponentModel.DataAnnotations):
using System.ComponentModel.DataAnnotations;
public class Movie
{
[Required]
public string Title { get; set; }
[Range(2000, 2022)]
public int Year { get; set; }
}
Code language: C# (cs)
Now create an object and populate it with data, and then validate it with the Validator class. The simplest option is to use the ValidateObject() method. Here’s an example. This deserializes JSON to an object, then validates the object:
using System.ComponentModel.DataAnnotations;
var movieJson = "{\"Year\":0}";
var movie = JsonSerializer.Deserialize<Movie>(movieJson);
Validator.ValidateObject(movie, new ValidationContext(movie), validateAllProperties: true);
Code language: C# (cs)
Note: It doesn’t matter how you populate the object. The data can come from anywhere (file, database, web API, etc…) in any format (JSON, CSV, XML, etc…). This is because validation is done AFTER the object is populated.
ValidateObject() throws an exception with the first validation error it encounters:
System.ComponentModel.DataAnnotations.ValidationException: The Title field is required.
If it’s important for you know ALL of the validation errors, then use TryValidationObject() instead, as I’ll show below. I’ll also explain how to deal with nested objects.
Note: You can even even use manual validation against your own custom validation attributes.
Table of Contents
When you want to see all validation errors
ValidateObject() fails fast by throwing an exception with the first validation error. If you want to know ALL of the validation errors, use TryValidateObject() instead. This puts the validation errors in a collection that you can then deal with as appropriate for your situation. Here’s an example of how to use TryValidateObject():
using System.ComponentModel.DataAnnotations;
var movieJson = "{\"Year\":0}";
var movie = JsonSerializer.Deserialize<Movie>(movieJson);
var validationErrors = new List<ValidationResult>();
if (!Validator.TryValidateObject(movie, new ValidationContext(movie), validationErrors, validateAllProperties: true))
{
//Look at all of the validation errors
foreach (var error in validationErrors)
{
Console.WriteLine(error.ErrorMessage);
}
}
Code language: C# (cs)
This outputs the following errors:
The Title field is required.
The field Year must be between 2000 and 2022.
Code language: plaintext (plaintext)
The validateAllProperties parameter
You may have noticed that I keep passing in validateAllParameters: true:
Validator.ValidateObject(movie, new ValidationContext(movie), validateAllProperties: true);
Code language: C# (cs)
This is because when validateAllParameters is false (which is the default), it only validates properties that have the [Required] attribute. In other words, it won’t validate other attributes (like [Range]). This is almost never what you want. You want it to check ALL validation attributes. So be sure to pass in true for validateAllProperties.
Validating when there’s nested objects
Validator doesn’t recursively validate nested objects. It only validates top-level properties. When you have nested objects, I suggest using RecursiveDataAnnotationsValidator (GitHub). It does recursive validation and uses Validator.TryValidateObject(). I’ll show how to use this.
Here’s an example of a class with a nested object that has properties to validate (Director.Name):
using System.ComponentModel.DataAnnotations;
public class Movie
{
[Required]
public string Title { get; set; }
[Range(2000, 2022)]
public int Year { get; set; }
[Required]
public Director Director { get; set; }
}
public class Director
{
[Required]
public string Name { get; set; }
}
Code language: C# (cs)
First, install the RecursiveDataAnnotationsValidator package (this is using View > Other Windows > Package Manager):
Install-Package RecursiveDataAnnotationsValidation
Code language: PowerShell (powershell)
Now here’s how to use it:
using RecursiveDataAnnotationsValidation;
var movieJson = "{\"Year\":0, \"Director\":{}}";
var movie = JsonSerializer.Deserialize<Movie>(movieJson);
var validator = new RecursiveDataAnnotationValidator();
var validationErrors = new List<ValidationResult>();
if (!validator.TryValidateObjectRecursive(movie, validationErrors))
{
//Handle errors however you want
foreach (var error in validationErrors)
{
Console.WriteLine(error.ErrorMessage);
}
}
Code language: C# (cs)
This outputs all of the validation errors, including the one from the nested object (Director.Name, highlighted):
The Title field is required.
The field Year must be between 2000 and 2022.
The Name field is required.
Code language: plaintext (plaintext)
Notice that it’s a bit simpler to use than using Validator directly. It abstracts away the details.
Fail fast recursive validation
You can solve this recursive validation problem yourself by using reflection to get the properties, then recurse through the properties and deal with special cases that might pop up. You don’t really *need* to use a third party library do this (but I do encourage it). Maybe you don’t want to use a third party library, or perhaps you want to do things a little differently. In any case, here’s an example of how to do recursive validation in the simplest way possible – fail fast by recursively calling Validator.ValidateObject():
using System.ComponentModel.DataAnnotations;
var movieJson = "{\"Year\":2018, \"Title\":\"Bob\", \"Director\":{}}";
var movie = JsonSerializer.Deserialize<Movie>(movieJson);
ValidateRecursively(movie, new HashSet<object>());
void ValidateRecursively(object o, HashSet<object> seen)
{
//Avoid getting caught in a cycle
if (seen.Contains(o))
return;
seen.Add(o);
Validator.ValidateObject(o, new ValidationContext(o), validateAllProperties: true);
foreach (var prop in o.GetType().GetProperties())
{
if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
RecurseValidate(prop.GetValue(o), seen);
}
}
Code language: C# (cs)
Note: This doesn’t handle all special cases – such as dealing with collection properties (arrays, lists, etc…) – but it can be used as a starting point. Deal with whatever special cases you need to deal with based on your actual classes.
This throws the following validation exception when it checks the Director.Name property:
System.ComponentModel.DataAnnotations.ValidationException: The Name field is required.
Dude this is so helpful – really appreciated! 🙂
You’re welcome, I’m glad it helped!
Great article… any idea how you would handle validation for those fields with ValueConverters?
Keep getting Unable to cast object of type ‘System.Boolean’ to type ‘System.String’. exception. If I remove the ValueConverter it works fine.
Hi Matt,
I haven’t used ValueConverters before. Are you using this with manual validation or automatic validation? Could you show me a code snippet so I can repro this behavior?