PAYATHON 2026

ASP.NET Core 验证消息为何全部显示默认无效值提示?

数据小许

结论

仅凭现有配置和模型代码,无法确定唯一原因。The value '' is invalid. 是框架可能生成的默认提示,不能由此断定所有验证特性都没有读取到塞尔维亚语资源。

先检查 ModelState 中保存的实际错误,再分别排查资源消息为空、模型绑定失败和页面显示异常的问题。重复调用 AddDataAnnotationsLocalization() 通常无法解决这些问题。

先确认默认消息从哪里产生

ASP.NET Core 显示字段验证错误时,如果某个 ModelError.ErrorMessage 为空,框架会调用 ValueIsInvalidAccessor 生成后备消息。因此,页面出现默认提示时,原始错误可能没有可显示的文本。ASP.NET Core 的 ValidationHelpers 源码中可以看到这一行为。

在处理表单提交的 Action 或 Razor Pages OnPost 中,重新显示页面之前加入以下临时诊断代码,通过断点查看结果:

var diagnostics = ModelState
    .Select(entry => new
    {
        Field = entry.Key,
        Errors = entry.Value!.Errors.Select(error => new
        {
            Message = error.ErrorMessage,
            Exception = error.Exception
        }).ToArray()
    })
    .Where(entry => entry.Errors.Length > 0)
    .ToArray();

重点区分以下情况:

  • Message 为空:检查资源值、自定义验证结果,以及是否有代码向 ModelState 添加空消息。
  • Exception 不为空:在开发环境查看异常及其内部异常,确认是否涉及类型转换或其他绑定问题。
  • Message 已经是正确的塞尔维亚语:继续检查 Razor 视图、客户端验证脚本,以及手动覆盖错误文本的代码。

这些诊断数据只用于开发排查,不要直接返回给浏览器。

单独检查资源消息是否有效

现有的 ErrorMessageResourceType 与 ErrorMessageResourceName 写法可以用于资源化验证消息。先绕过表单和客户端脚本,直接检查一个内置验证特性的消息:

using System.ComponentModel.DataAnnotations;
using System.Globalization;

// 放在处理当前请求的方法中,通过断点检查变量。
var uiCulture = CultureInfo.CurrentUICulture.Name;

var attribute = new RequiredAttribute
{
    ErrorMessageResourceName = "field_required",
    ErrorMessageResourceType = typeof(Resources.DataAnnotations)
};

var message = attribute.FormatErrorMessage("Email");
var hasMessage = !string.IsNullOrWhiteSpace(message);

如果 message 为空或只有空白,检查 DataAnnotations.sr.resx 中 field_required 的值,也检查回退资源 DataAnnotations.resx。资源键存在但值为空时,无法提供有效的验证提示。

如果调用抛出异常,根据异常检查资源类、资源属性名称、属性类型和消息格式。例如,field_required 必须对应资源类中可供该机制访问的静态字符串属性,资源文本中的 {0} 等格式占位符也必须正确。资源引用不合法可能直接抛出异常,不能一概解释成默认提示。ValidationAttribute 源码说明了资源属性查找和错误消息配置的处理方式。

这里使用的是强类型资源类。ResourcesPath = "Resources" 不能代替对该资源类、生成代码和嵌入资源的检查。

明确配置请求语言

SetDefaultCulture("sr") 设置默认语言。为了明确声明应用支持的语言,可以补充 SupportedCultures 和 SupportedUICultures:

builder.Services.AddLocalization(options =>
    options.ResourcesPath = "Resources");

builder.Services.AddControllersWithViews()
    .AddDataAnnotationsLocalization();

builder.Services.AddRazorPages();

var app = builder.Build();

app.UseRequestLocalization(options =>
{
    options.SetDefaultCulture("sr")
        .AddSupportedCultures("sr")
        .AddSupportedUICultures("sr");
});

// 保留项目已有的其他中间件和端点配置。

本地化中间件应在需要读取请求语言的处理逻辑之前执行。资源语言主要看 CultureInfo.CurrentUICulture,日期等格式处理还涉及 CultureInfo.CurrentCulture。应在发生问题的请求中检查它们的实际值。Microsoft 的请求语言配置文档介绍了默认语言、支持语言及请求语言提供程序的关系。

这项配置可以明确语言选择,但不能证明当前故障由它引起。

单独处理 BirthDate 的绑定问题

BirthDate 是不可空的 DateOnly。空字符串无法转换成有效日期时会产生模型绑定错误;[Required] 也无法把已经存在的值类型默认值视为 null。模型绑定错误与 DataAnnotations 验证消息属于不同环节。Microsoft 模型绑定文档说明了类型转换失败时的处理方式。

如果表单需要用 [Required] 表达“必须填写生日”,可以考虑:

[DisplayName("birthDate")]
[DataType(DataType.Date)]
[Required(
    ErrorMessageResourceName = "field_required",
    ErrorMessageResourceType = typeof(Resources.DataAnnotations))]
[Adult(
    ErrorMessageResourceName = "birthDate_adult",
    ErrorMessageResourceType = typeof(Resources.DataAnnotations))]
public DateOnly? BirthDate { get; set; }

前提是 Adult 支持 DateOnly?,并让空值交给 [Required] 处理。用户输入无法解析的日期时,仍可能产生绑定错误,需要另行处理。

这个改动只针对生日字段,不能解释所有字符串字段为什么也显示相同提示。

最后检查自定义验证与页面输出

题目没有提供 Alpha、Adult、表单视图和提交处理代码,还需要检查这些位置:

  1. 自定义验证特性生成失败消息时,应使用配置的资源消息。如果手动构造 ValidationResult,可以使用 FormatErrorMessage(validationContext.DisplayName),避免返回空文本或固定英文。
  2. 检查是否有代码把错误重新写入 ModelState,导致原有消息丢失。
  3. 核对字段名与 asp-for、asp-validation-for 是否对应。例如 Email 字段可以使用:
<input asp-for="Email" />
<span asp-validation-for="Email"></span>

如果使用了客户端验证,再检查生成的 HTML 中 data-val-required、data-val-email 等属性是否已包含正确消息。如果属性正确而页面提示错误,应重点排查客户端脚本。

如果最终确认需要翻译模型绑定的默认提示,应通过 ModelBindingMessageProvider 的相应 Set...Accessor 方法配置。它与 [Required] 等验证特性的资源消息是两套配置。直接翻译后备提示,无法修复资源文本为空或错误被覆盖的问题。相关 API 文档列出了各类消息对应的设置方法。

备注:内容仅供参考。