Reputation: 666
Been trying to incorporate server side DataAnnotation validation to my project and I've found out that DataAnnotations has it's own type of error, the ValidationException. My problem with it, though, is that it only returns one validation error at a time so if 3 properties failed validation only the first one is thrown. I'm looking for a way to throw all of the errors as an exception so instead of informing the user/developer that the validation failed, it would state which properties/fields failed validation at one go.
I found the Validator.TryValidateObject(...) method but it just populates ValidationResults and leaves the developer an option to throw and exception or not. What I currently implement is iterating through the ValidationResults to create a list of ValidationExceptions from that, wrap the list into an AggregateException then throw another ValidationException with the AggregateException in its InnerExceptions.
ValidationContext validationContext = new ValidationContext(entity, null, null);
List<ValidationResult> validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(entity, validationContext, validationResults, true);
if (!isValid)
{
List<ValidationException> validationErrors = new List<ValidationException>();
foreach (ValidationResult validationResult in validationResults)
{
validationErrors.Add(new ValidationException(validationResult.ErrorMessage);
}
throw new ValidationException("Entity validation failed.", new AggregateException(validationErrors));
}
So basically, my questions would be:
Upvotes: 13
Views: 6060
Reputation: 565
Answer your first question:
Because each validation attribute have their own IsValid
property and return public ValidationResult(string errorMessage, IEnumerable<string> memberNames);
validation result and you can get member name from list of member name. So each validation failed of property return isValid. It is not a good idea that the entity has validation you are applying on property of entity.
Answer your second question:
You can create your own ValidationAttribute
list to validate entity:
var validationResults = new List<ValidationResult>();
var validationAttributes = new List<ValidationAttribute>();
validationAttributes.Add(new CustomValidationAttribute(typeof(ClaimValidator), "ValidateClaim"));
var result = Validator.TryValidateValue(claimObject,
new ValidationContext(claimObject, null, null),
validationResults,
validationAttributes);
Third Answer:
You can get the member name from ValidationResult
:
public ValidationResult(string errorMessage, IEnumerable<string> memberNames)
Upvotes: 2
Reputation: 557
Regarding your first question, the standard answer to "why doesn't the language support X" is just cost-versus-benefit, and I'm sure that applies here as well. It's not often that people run into scenarios that require throwing multiple errors... So the folks who designed and implemented C# figured that the time it would take the C# team to design and implement the "throw multiple exceptions" feature would be better spent on features that provide more benefit to more people.
And consider the cost for language users - everywhere you currently have "catch (Exception e)" you'd have to do "catch (IEnumerable exceptions)" instead, followed by a foreach, in case the API that you called threw multiple exceptions.
Regarding your second question, I think that combining your ValidationExceptions with an AggregateException and setting the aggregate as the InnerException of a top-level ValidationException is a fairly elegate way to approach the problem... But it's not standard, so hopefully the people on the "catch" side of that exception hierarchy are people that you're in close communication with.
You might also consider storing your ValidationResult collection in the Data property of a ValidationException. That's a bit simpler. The Data property is not widely used, but it exists for non-standard scenarios like this one.
It's hard to say which of those two approaches is better, but I lean toward the first one. It's more complicated, but I think the likelihood of an arbitrary coder discovering what you've done is a little higher if you build on InnerException and AggregateException. I mean, when's the last time you dumped the contents of Exception.Data while you were debugging something? Yeah, me neither. :-)
Regarding your third question, I think I'd just build the ValidationException's message based on the contents of the ValidationResult. Something like "Member {0} is invalid: {1}" for example.
Upvotes: 0