1

In a .NET Core 3.x application, I have a model like the following where a custom validation attribute [CustomValidator] is used. The error messages returned for CustomValidator doesn't have a $. prefix whereas the built-in JSON validator returns with the prefix. I'd like to have them consistent (either with $. prefix all the time or not). Any idea how could this be done?

Example model:

public class Dto 
{
    public float Price {get; set;}
    [CustomValidator] public int? EntityID {get; set;}
}

where [CustomValidator] is a custom validation attribute which does something like this

public class CustomValidatorAttribute : ValidationAttribute
{

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {

          var isValid = CheckSomething(...);
          return isValid
                ? ValidationResult.Success
                : new ValidationResult($"Invalid value for Entity");
    }

}

I validate model in the Controller.Action with

if (!ModelState.IsValid) return BadRequest(ModelState);

For the input:

{
  "Price" : "abc"
}

it returns

{
...
    "errors": {
        "$.price": [
            "The JSON value could not be converted to System.Single. Path: $.price | LineNumber: 7 | BytePositionInLine: 21."
        ]
    }
}

whereas for the input:

{
  "Price" : 1.0,
  "EntityID": -1,
}

it returns

{
...
    "errors": {
        "EntityID": [
            "Invalid value for Entity"
        ]
    }
}

I'd like it to have the errors always having consistent property names e.g. Price and EntityID instead of $.price and EntityID

1 Answer 1

3

The $. prefix is part of the json path contained in JsonException. You can see it in the source code of the default json input formatter here SystemTextJsonInputFormatter.

So technically to normalize that path before it being passed to the method ModelState.TryAddModelError, you need of course to modify that path somehow. It's quite complicated that way. Instead you can configure a custom InvalidModelStateResponseFactory for your final ProblemDetails result. Although this way you may take a tradeoff of performance hit especially when the validation error occurs frequently and there are a lot of errors involved. But I think it's not a common case in reality. The idea of this approach is to modify the ModelState by trying to swap out all the entries having key prefixed with $. and replace them with the ones having that key prefix trimmed off.

You can configure that response factory by configuring the ApiBehaviorOptions, like this:

//inside Startup.ConfigureServices
services.Configure<ApiBehaviorOptions>(o => {
            //we need to call this original factory inside our custom factory
            var originalFactory = o.InvalidModelStateResponseFactory;
            o.InvalidModelStateResponseFactory = context => {   
                //get all the keys prefixed with $. (which should be related to json errors)                 
                var jsonPathKeys = context.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList();
                foreach(var key in jsonPathKeys)
                {
                    var normalizedKey = key.Substring(2);
                    foreach (var error in context.ModelState[key].Errors)
                    {
                        if (error.Exception != null)
                        {
                            context.ModelState.TryAddModelException(normalizedKey, error.Exception);
                        }
                        context.ModelState.TryAddModelError(normalizedKey, error.ErrorMessage);
                    }
                    context.ModelState.Remove(key);
                }                    
                return originalFactory(context);
            };
        });
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.