Skip to content

How to Customize Default Model Validation Error Messages in ASP.NET Core

TLDR

  • ASP.NET Core's default Model Validation only provides English messages; these can be customized using resource files (.resx) and IValidationMetadataProvider.
  • Validation messages are divided into "ModelBinding" and "ValidationMetadata" parts, which require separate implementation and configuration.
  • The ModelBindingMessageProvider can be used to directly override ModelBinding error messages.
  • By implementing the IValidationMetadataProvider interface, you can dynamically replace the default error messages of ValidationAttribute.
  • To support multiple languages, create corresponding resource files and configure SupportedUICultures via RequestLocalizationOptions to enable automatic switching.
  • When setting the culture, distinguish between Culture (formatting settings) and UICulture (resource file loading); the display of error messages depends on UICulture.

Handling ModelBinding and ValidationMetadata Validation Messages

In ASP.NET Core, the sources of Model Validation error messages are divided into two categories, handled as follows:

1. Create Resource Files (.resx)

Create resource files to store custom error messages, set the property to "Embedded Resource", and configure access modifiers as needed.

  • ModelBindingMessage: Handles data format-related errors (e.g., type conversion failures).
  • ValidationMetadataMessage: Handles data content validation errors (e.g., Required, Range, and other attributes).

2. Customize ValidationMetadataProvider

To replace the default error messages of ValidationAttribute, you must implement IValidationMetadataProvider.

When to use this: When you want to uniformly modify the default English error messages for all ValidationAttribute instances (such as RequiredAttribute) in your project without repeatedly writing ErrorMessage on every property.

csharp
public class LocalizationValidationMetadataProvider : IValidationMetadataProvider {
    private readonly ResourceManager resourceManager;
    private readonly Type resourceType;

    public LocalizationValidationMetadataProvider(Type type) {
        resourceType = type;
        resourceManager = new ResourceManager(type);
    }

    public void CreateValidationMetadata(ValidationMetadataProviderContext context) {
        foreach (var attribute in context.ValidationMetadata.ValidatorMetadata.OfType<ValidationAttribute>()) {
            if (attribute.ErrorMessageResourceName is null) {
                bool hasErrorMessage = attribute.ErrorMessage != null;

                if (hasErrorMessage) {
                    string? defaultErrorMessage = typeof(ValidationAttribute)
                        .GetField("_defaultErrorMessage", BindingFlags.NonPublic | BindingFlags.Instance)
                        ?.GetValue(attribute) as string;

                    hasErrorMessage = attribute.ErrorMessage != defaultErrorMessage;
                }

                if (hasErrorMessage) {
                    continue;
                }

                string? name = GetMessageName(attribute);
                if (name != null && resourceManager.GetString(name) != null) {
                    attribute.ErrorMessageResourceType = resourceType;
                    attribute.ErrorMessageResourceName = name;
                    attribute.ErrorMessage = null;
                }
            }
        }
    }

    private string? GetMessageName(ValidationAttribute attr) {
        switch (attr) {
            case CompareAttribute _:
                return "CompareAttribute_MustMatch";
            case StringLengthAttribute vAttr:
                if (vAttr.MinimumLength > 0) {
                    return "StringLengthAttribute_ValidationErrorIncludingMinimum";
                }
                return "StringLengthAttribute_ValidationError";
            case DataTypeAttribute _:
                return $"{attr.GetType().Name}_Invalid";
            case ValidationAttribute _:
                return $"{attr.GetType().Name}_ValidationError";
        }
        return null;
    }
}

3. Register Services

Register the provider above and configure ModelBindingMessageProvider in Program.cs.

csharp
builder.Services.AddRazorPages()
    .AddMvcOptions(options => {
        var provider = options.ModelBindingMessageProvider;
        provider.SetAttemptedValueIsInvalidAccessor((x, y) => string.Format(ModelBindingMessage.AttemptedValueIsInvalid, x, y));
        provider.SetMissingBindRequiredValueAccessor(x => string.Format(ModelBindingMessage.MissingBindRequiredValue, x));
        // ... Other ModelBinding message settings
        
        options.ModelMetadataDetailsProviders.Add(new LocalizationValidationMetadataProvider(typeof(ValidationMetadataMessage)));
    });

Implementing Multi-language Support

When to use this: When the application needs to dynamically display validation error messages in the corresponding language based on the user's language settings.

Configuration Steps

  1. Create resource files for the corresponding languages, such as ModelBindingMessage.zh-TW.resx.
  2. Set the resource file property to "Embedded Resource" and "Do not generate code".
  3. Configure RequestLocalizationOptions in Program.cs.
csharp
WebApplication app = builder.Build();

string[] supportedCultures = new string[] { "zh-TW", "en-US" };
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

INFO

Difference between Culture and UICulture

  • Culture: Determines the formatting and sorting rules for dates, numbers, and currency.
  • UICulture: Determines which language's resource file to load (error message display depends on this setting).
  • If using QueryString to pass the language, the correct parameter should be ui-culture rather than culture.

Change Log

  • 2022-10-05 Initial document creation.
  • 2024-04-04 Fixed messages in ModelBindingMessage.