I've been working on an application that uses xVal's server side validation with data annotations. We recently ran into errors where the validation messages have been unpredictable for fields that have multiple validations that could fail if the field is empty (e.g., an email address is required, but also fails a validity check).
Assuming that I needed to just return the first validation error, I added a method to our validation runner to achieve that goal (UPDATE: see edit at the bottom for the exact method):
public static IEnumerable<ErrorInfo> GetFirstErrors<T>(object instance) where T : ValidationAttribute
{
return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<T>().Take(1)
where !attribute.IsValid(prop.GetValue(instance))
select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
}
I also set up a simple test method to verify in NUnit:
private class FirstErrorValidationTest
{
[RequiredValueValidator(ErrorMessage = "This field is required"), StringLength(50)]
public string FirstName { get; set; }
[RequiredValueValidator(ErrorMessage = "This field is required"), StringLength(50)]
public string LastName { get; set; }
[RequiredValueValidator(ErrorMessage = "This field is required"), EmailAddressValidator, StringLength(50)]
public string EmailAddress { get; set; }
}
[Test]
public void Assert_GetFirstErrors_Gets_First_Listed_Validation_Attribute_Error_Messages()
{
FirstErrorValidationTest test = new FirstErrorValidationTest()
{
FirstName = "",
LastName = "",
EmailAddress = ""
};
var errors = DataAnnotationsValidationRunner.GetFirstErrors(test);
Assert.AreEqual(3, errors.Count());
foreach (var error in errors)
Assert.IsTrue(error.ErrorMessage.Contains("required"));
}
The problem is that this test's output is highly unpredictable. Sometimes it passes, sometimes it returns just one or two of the errors, and sometimes none at all. Is the issue here with my LINQ query, my test, or both?
EDIT: Great point on pasting in a slightly different method; here's the one actually being hit:
public static IEnumerable<ErrorInfo> GetFirstErrors(object instance)
{
return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>().Take(1)
where !attribute.IsValid(prop.GetValue(instance))
select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
}