I've overridden all model binding messages with translations using ModelBindingMessageProvider.SetValueIsInvalidAccessor
and other ModelBindingMessageProvider values to return my custom resource strings.
And then I discovered the sad truth. If my API controller receives the data as JSON, then ModelBindingMessageProvider validation messages are not being used. Instead, Json.Net kicks in and I get something like this in response:
"errors": {
"countryId": [
"Input string '111a' is not a valid number. Path 'countryId', line 3, position 23."
]
},
I looked in GitHub source of Json.Net - indeed, it seems to have such exact error messages defined with line numbers etc.
So, ModelState manages to pull them in instead of using its own ModelBindingMessageProvider messages.
I tried to disable Json.Net error handling:
.AddJsonOptions(options =>
{
...
options.SerializerSettings.Error = delegate (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
{
// ignore them all
args.ErrorContext.Handled = true;
};
})
but it made no difference.
Is there any way to catch these Json deserialization errors and redirect them to ModelBindingMessageProvider, so that my localized messages would work?
Is there any way to catch these Json deserialization errors and redirect them to ModelBindingMessageProvider, so that my localized messages would work?
No, model binding and json input are different, model binder is for FromForm
, and JsonInputFormatter is for FromBody
. They are following different way. You could not custom the error message from ModelBindingMessageProvider
.
For JSON
, you may implement your own JsonInputFormatter
and change the error message like
CustomJsonInputFormatter
public class CustomJsonInputFormatter : JsonInputFormatter
{
public CustomJsonInputFormatter(ILogger<CustomJsonInputFormatter> logger
, JsonSerializerSettings serializerSettings
, ArrayPool<char> charPool
, ObjectPoolProvider objectPoolProvider
, MvcOptions options
, MvcJsonOptions jsonOptions)
: base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions)
{
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var result = await base.ReadRequestBodyAsync(context);
foreach (var key in context.ModelState.Keys)
{
for (int i = 0; i < context.ModelState[key].Errors.Count; i++)
{
var error = context.ModelState[key].Errors[i];
context.ModelState[key].Errors.Add($"This is translated error { error.ErrorMessage }");
context.ModelState[key].Errors.Remove(error);
}
}
return result;
}
}
Register CustomJsonInputFormatter
services.AddMvc(options =>
{
var serviceProvider = services.BuildServiceProvider();
var customJsonInputFormatter = new CustomJsonInputFormatter(
serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<CustomJsonInputFormatter>(),
serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value.SerializerSettings,
serviceProvider.GetRequiredService<ArrayPool<char>>(),
serviceProvider.GetRequiredService<ObjectPoolProvider>(),
options,
serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value
);
options.InputFormatters.Insert(0, customJsonInputFormatter);
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Register localized Service into CustomJsonInputFormatter
to custom the error message.