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.