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
's Address
with no value for PostalCode
, 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?
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.
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);
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 />
Source: https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-3.1#blazor-data-annotations-validation-package
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With