How to localize standard error messages of validation attributes in ASP.NET Core (v2.2)? For Example, [Required] attribute has this error message "The xxx field is required."; [EmailAddress] has "The xxx field is not a valid e-mail address."; [Compare] has "'xxx' and 'yyy' do not match." and so on. In our project we use not English language and I want to find a way how to translate standard error messages without writing them directly in every attribute of every data-model class
2 Answers
If you just want to localize the error messages but not to build a multi-language site, you may try this: (the message strings may be in your language.)
- Add a custom
IValidationMetadataProvider:
public class MyModelMetadataProvider : IValidationMetadataProvider
{
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException();
}
var validators = context.ValidationMetadata.ValidatorMetadata;
// add [Required] for value-types (int/DateTime etc)
// to set ErrorMessage before asp.net does it
var theType = context.Key.ModelType;
var underlyingType = Nullable.GetUnderlyingType(theType);
if (theType.IsValueType &&
underlyingType == null && // not nullable type
validators.Where(m => m.GetType() == typeof(RequiredAttribute)).Count() == 0)
{
validators.Add(new RequiredAttribute());
}
foreach (var obj in validators)
{
if (!(obj is ValidationAttribute attribute))
{
continue;
}
fillErrorMessage<RequiredAttribute>(attribute,
"You must fill in '{0}'.");
fillErrorMessage<MinLengthAttribute>(attribute,
"Min length of '{0}' is {1}.");
fillErrorMessage<MaxLengthAttribute>(attribute,
"Max length of '{0}' is {1}.");
fillErrorMessage<EmailAddressAttribute>(attribute,
"Invalid email address.", true);
// other attributes like RangeAttribute, CompareAttribute, etc
}
}
private void fillErrorMessage<T>(object attribute, string errorMessage,
bool forceOverriding = false)
where T : ValidationAttribute
{
if (attribute is T validationAttribute)
{
if (forceOverriding ||
(validationAttribute.ErrorMessage == null
&& validationAttribute.ErrorMessageResourceName == null))
{
validationAttribute.ErrorMessage = errorMessage;
}
}
}
}
- add some lines in
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddMvcOptions(m => {
m.ModelMetadataDetailsProviders.Add(new MyModelMetadataProvider());
m.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(
fieldName => string.Format("'{0}' must be a valid number.", fieldName));
// you may check the document of `DefaultModelBindingMessageProvider`
// and add more if needed
})
;
}
see the document of DefaultModelBindingMessageProvider
If you can read in Japanese, see this article for more details.
1 Comment
Dan Z
Thanks! Works fine in ASP.NET Core 7.0. BTW, defining a custom EmailAddressAttribute error message won't have any effect in all major browsers, because ASP.NET Core adds the type="email" to the input, which triggers the browser's validation, and if it doesn't pass, the browser displays its own error message (in the browser's current language) and the form isn't submitted
This is spelled out in the docs. You can do either:
Use the
ResourcePathoption on the attribute.[Required(ResourcePath = "Resources")]
Then, you'd add the localized message to Resources/Namespace.To.MyClass.[lang].resx.
Use one resource file for all classes:
public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddDataAnnotationsLocalization(options => { options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResource)); }); }
2 Comments
Vitaliy
looks like this approach expects ErrorMessage parameter for all attributes in all models to be translated. I want to avoid it
Frédéric
Such a let-down from how it was handled in .Net Framework, which was providing globalization out of the box for this. It baffles me all core projects have to translate and globalize default validation logic themselves instead of having it out of the box. But all issues I found about that were closed without being done. I have tried opening a new one.