I have a simple web API that performs some basic validation using validation attributes. If the request does not meet the validation requirements, I then extract the error messages from the ModelStateDictionary
and return these to the caller as a dictionary, where the key is the path to the property that has an error and the message indicates what the problem is.
The problem is that, given certain input's, the error messages in the ModelStateDictionary
often contains information that I would not want to return to the client (such as the full name (including namespace) of the object that the converter attempted to map the JSON to).
Is it possible to differentiate between validation errors (generated by Validation Attributes / IValidatableObject implementations) and errors generated by attempting to map invalid JSON to a certain object?
For example:
My models
public class Order
{
[Required]
public Customer Customer { get; set; }
}
public class Customer
{
[Required]
public string Name { get; set; }
}
My action method
public IActionResult Post(Order order)
{
if (!ModelState.IsValid)
{
var errors = GetErrors(ModelState);
return BadRequest(errors);
}
return Ok();
}
private Dictionary<string, string> GetErrors(ModelStateDictionary modelState)
{
var errors = new Dictionary<string, string>();
foreach (var error in modelState)
{
string message = null;
if (error.Value.Errors.Any(e => e.Exception != null))
{
message = "Unable to interpret JSON value.";
}
else
{
message = string.Join(". ", string.Join(". ", error.Value.Errors.Select(e => e.ErrorMessage)));
}
errors.Add(error.Key, message);
}
return errors;
}
My example inputs:
- Missing required data
{
"Customer": {
"Name": null // name is required
}
}
Because Customer.Name
is decorated with the RequiredAttribute
, this generates an error message of:
{
"Customer.Name": "The Name field is required."
}
This is something that's OK to return to the caller, so no issues here.
- JSON value doesnt map to .Net object type
{
"Customer": 123 // deserializing will fail to map this value to a Customer object
}
Because deserializing the value 123
to a Customer
object fails, this generates an error message of:
{
"Customer": "Error converting value 123 to type 'MyProject.Models.Customer'. Path 'Customer', line 2, position 19."
}
This is not OK to return to the caller, as it contains the full namespace path to the object being mapped to. In this case, something generic (such as "Bad JSON value.") should be used as the error message.
Can anyone help me find a solution to hide these error messages which contain information that should not be returned to the caller?
As can be seen in my code above, I thought I might be able to check the ModelEntry.Exception
property and use this to determine whether the error message needs to be shielded from the caller, but this is null in both my examples.
On solution may be to check if the error message starts with Error converting value
, and if so, shield the message from the caller. This doesnt seem very robust, and I'm not sure how reliable this will be in a real-world example.
Typically when model binding fails because the JSON payload is not properly formatted, then the incoming model object ("order" in your case) will be
null
. Just put in a simple null check and return BadRequest with a generic error message.