DataAnnotations: Recursively validating an entire object graph
Solution 1
Here's an alternative to the opt-in attribute approach. I believe this will traverse the object-graph properly and validate everything.
public bool TryValidateObjectRecursive<T>(T obj, List<ValidationResult> results) {
bool result = TryValidateObject(obj, results);
var properties = obj.GetType().GetProperties().Where(prop => prop.CanRead
&& !prop.GetCustomAttributes(typeof(SkipRecursiveValidation), false).Any()
&& prop.GetIndexParameters().Length == 0).ToList();
foreach (var property in properties)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) continue;
var value = obj.GetPropertyValue(property.Name);
if (value == null) continue;
var asEnumerable = value as IEnumerable;
if (asEnumerable != null)
{
foreach (var enumObj in asEnumerable)
{
var nestedResults = new List<ValidationResult>();
if (!TryValidateObjectRecursive(enumObj, nestedResults))
{
result = false;
foreach (var validationResult in nestedResults)
{
PropertyInfo property1 = property;
results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
}
};
}
}
else
{
var nestedResults = new List<ValidationResult>();
if (!TryValidateObjectRecursive(value, nestedResults))
{
result = false;
foreach (var validationResult in nestedResults)
{
PropertyInfo property1 = property;
results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x)));
}
}
}
}
return result;
}
Most up-to-date code: https://github.com/reustmd/DataAnnotationsValidatorRecursive
Package: https://www.nuget.org/packages/DataAnnotationsValidator/
Also, I have updated this solution to handle cyclical object graphs. Thanks for the feedback.
Solution 2
You can extend the default validation behavior, making the class you want to validate implement the IValidatableObject
interface
public class Employee : IValidatableObject
{
[Required]
public string Name { get; set; }
[Required]
public Address Address { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
Validator.TryValidateObject(Address, new ValidationContext(Address), results, validateAllProperties: true);
return results;
}
}
public class Address
{
[Required]
public string Line1 { get; set; }
public string Line2 { get; set; }
[Required]
public string Town { get; set; }
[Required]
public string PostalCode { get; set; }
}
And validate it using the Validator
class in one of these ways
Validator.ValidateObject(employee, new ValidationContext(employee), validateAllProperties: true);
or
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(employee, new ValidationContext(employee), validationResults, validateAllProperties: true);
Solution 3
I found this issue while searching for a similar problem I had with Blazor. Seeing as Blazor is becoming increasingly more popular I figured this would be a good place to mention how I solved this problem.
Firstly, install the following package using your package manager console: Install-Package Microsoft.AspNetCore.Components.DataAnnotations.Validation -Version 3.2.0-rc1.20223.4
Alternatively you can also add it manually in your .csproj file:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
</ItemGroup>
Having added and installed this package one can simply add the following data annotation to any object to indicate that it is a complex type. Using the example OP provided:
public class Employee
{
[Required]
public string Name { get; set; }
[ValidateComplexType]
public Address Address { get; set; }
}
public class Address
{
[Required]
public string Line1 { get; set; }
public string Line2 { get; set; }
[Required]
public string Town { get; set; }
[Required]
public string PostalCode { get; set; }
}
Take note of the [ValidateComplexType]
annotation above the Address
reference.
For the ones that also found this post when using Blazor: make sure your EditForm uses this AnnotationValidator instead of the normal one:
<ObjectGraphDataAnnotationsValidator />
Neil Barnwell
I'm an Application Developer and Software Architect for a wide variety of software solutions including websites and desktop applications. Most recently focussed on a bespoke warehouse management system using C# 3.5 and SQL Server 2008. Since my move to .NET I've become active in the community, attending monthly usergroup meetings and various conferences. I've even made a foray into speaking at usergroups about topics I am passionate about. While I love experimenting with new tech and have a hobby project hosted on CodePlex, my current focus is less on specific new technologies and more on good principles and techniques. When not at work I'm a family man, biker, amateur photographer, guitarist and of course, software developer.
Updated on July 05, 2022Comments
-
Neil Barnwell almost 2 years
I have an object graph sprinkled with DataAnnotation attributes, where some properties of objects are classes which themselves have validation attributes, and so on.
In the following scenario:
public class Employee { [Required] public string Name { get; set; } [Required] public Address Address { get; set; } } public class Address { [Required] public string Line1 { get; set; } public string Line2 { get; set; } [Required] public string Town { get; set; } [Required] public string PostalCode { get; set; } }
If I try to validate an
Employee
'sAddress
with no value forPostalCode
, then I would like (and expect) an exception, but I get none. Here's how I'm doing it:var employee = new Employee { Name = "Neil Barnwell", Address = new Address { Line1 = "My Road", Town = "My Town", PostalCode = "" // <- INVALID! } }; Validator.ValidateObject(employee, new ValidationContext(employee, null, null));
What other options do I have with
Validator
that would ensure all properties are validated recursively? -
Jorrit Schippers about 12 yearsI like this solution, but be beware of infinite loops when the object graph contains cycles.
-
rogersillito almost 8 yearsThe code sample above has a few issues compared to the git version - so definitely follow the link if you're looking to implement this (or
Install-Package dataannotationsvalidator
via nuget!) -
reustmd almost 8 years@rogersillito Updated the code and added links to the answer. Thanks!
-
Hans Vonn almost 7 yearsI agree with @JorritSchippers about the infinite loops. This tool would be a preferable solution if this was addressed.
-
reustmd almost 7 years@HansVonn I have update the code to handle cyclical object graphs. I'm working on getting it published in nuget, but there's a separate owner so it may take a bit.
-
reustmd almost 7 years@JorritSchippers I have update the code to handle cyclical object graphs. I'm working on getting it published in nuget, but there's a separate owner so it may take a bit.
-
Rick M. over 5 yearsWelcome to SO! Although this might answer the question, adding explanation at necessary steps and supporting your claims with suitable links will add weight to the answer and be more helpful!
-
Shimmy Weitzhandler over 4 yearsCan you reflect the validation in the UI in this manner (plain DA attributes)?
-
mrdnk about 4 yearsThis is an answer to a very old post. In my opinion, this is the wrong way of approaching a fairly simple problem, that actually needs to be very open and explicit. You essentially want to approach this as domain or request validation question. I'd personally implement a customer Validator class.