C# – Manually validate objects that have model validation attributes

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, like this:

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.

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. 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, recursion, 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.

Leave a Comment