The problem
Validate incoming API calls and return nice errors if some property failed to validate.
Example:
public class AuthorInput
{
public NameInput Name { get; set; }
}
public class NameInput
{
public string FirstName { get; set; }
public string SurName { get;set; }
}
I've removed all validation logic from the above class, but FirstName
and SurName
are required.
So if someone would post the following body...
{
"name": {
"surName": "Ottosson"
}
}
...the API should return something like this:
{
"errors": [
{
"type": "VALIDATION_ERROR",
"message": "Property 'FirstName' is required",
"path": "Name.FirstName"
}
]
}
How do you create the dot-notation path?
"Solution 1"
if(validationResult.IsFailure)
{
// Go through all properties
....
var firstNamePropertyPath = "Name.FirstName"
...
}
This is obviously not a good solution :)
Solution 2
if(validationResult.IsFailure)
{
// Go through all properties
....
var firstNamePropertyPath = $"{nameof(Author.Name)}.{nameof(Author.Name.FirstName)}";
...
}
Now we get help from the compiler, but it can be a bit ugly when we have several levels of nesting.
Solution 3
if(validationResult.IsFailure)
{
// Go through all properties
....
var sections = new []
{
nameof(Author.Name),
nameof(Author.Name.FirstName)
};
var firstNamePropertyPath = string.Join('.', sections);
...
}
Maybe a bit more clear? But still not good.
Solution 4
if(validationResult.IsFailure)
{
// Go through all properties
....
var firstNamePropertyPath = _propertyPathFactory.Create<Author>(x => x.Name.FirstName);
...
}
Now we're talking :)
PropertyPathFactory
looks like this
public string Create<T>(Expression<Func<T, object>> pathExpression)
{
var getMemberNameFunc = new Func<Expression, MemberExpression>(expression => expression as MemberExpression);
var memberExpression = getMemberNameFunc(pathExpression.Body);
var names = new Stack<string>();
while (memberExpression != null)
{
names.Push(memberExpression.Member.Name);
memberExpression = getMemberNameFunc(memberExpression.Expression);
}
return string.Join('.', names);
}
FluentValidation does something similar here.
This was just a quick writeup, do you guys know any better way to do it? It would be awesome if you could get the FULL path from nameof(Author.Name.FirstName)
and not just the last section.