ASP.NET Core 模型验证消息的本地化新姿势
<p>最近在研究系统本地化的问题,不可避免要实现模型类的验证消息本地化。毕竟这些错误消息是要返回给用户的。</p><p><strong style="font-size: 18px">疑问产生</strong></p>
<p>在MVC模型下,我们会使用模型类对请求参数进行绑定和验证。举个例子:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UserDto
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name{<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">;}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span>? Age {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>这样本身没有什么问题,但如果有大量模型要做本地化改造,那可就是个大工程了。</p>
<p>我们不禁要问,为什么要指定ErrorMessage,<strong>默认的错误消息</strong>不能用吗?毕竟我们人工指定的错误消息除了字段名之外,其它都完全一样,实在没有必要逐个指定。</p>
<p><strong style="font-size: 18px">默认消息</strong></p>
<p>我们来改造一下看看,删除掉指定的ErrorMessage。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UserDto
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name{<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">;}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span>? Age {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>如果没有传入参数导致验证不通过,会得到如下消息:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">The Name field is required.</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">The Age field is required.</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p>没错,默认消息是英文的,这对我们来说完全不可用——这对用户很不友好,难怪要人工设置 ErrorMessage。</p>
<p><span style="font-size: 18px"><strong>查找默认消息</strong></span></p>
<p>那有没有可能直接把默认消息本地化呢?如果可以,那就不用再麻烦地设置 ErrorMessage了。</p>
<p>通过查看<strong>官方源码</strong>我们发现,默认消息来自 SR 类,以RequiredAttribute举例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> RequiredAttribute()
: </span><span style="color: rgba(0, 0, 255, 1)">base</span>(() =><span style="color: rgba(0, 0, 0, 1)"> SR.RequiredAttribute_ValidationError)
{
}</span></pre>
</div>
<p> SR 类的内容简略如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">internal</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> SR
{
</span><span style="color: rgba(0, 0, 255, 1)">internal</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">global</span>::System.Resources.ResourceManager ResourceManager => s_resourceManager ?? (s_resourceManager = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">global</span>::System.Resources.ResourceManager(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(FxResources.System.ComponentModel.Annotations.SR)));
</span><span style="color: rgba(0, 0, 255, 1)">internal</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">string</span> @RequiredAttribute_ValidationError => GetResourceString(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RequiredAttribute_ValidationError</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">@"</span><span style="color: rgba(128, 0, 0, 1)">The {0} field is required.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p>上面的代码中,GetResourceString 最终会调用内部声明的 ResourceManager。而 ResourceManager 会根据传入的类型参数查找本地化资源。</p>
<p><span style="font-size: 18px"><strong>本地化默认消息</strong></span></p>
<p>通过上面的分析,如果要使用中文内容,我们只要把本地化的消息放入 <strong>FxResources.System.ComponentModel.Annotations.SR.zh-CN.resources</strong> 即可。动手之前,我们再确认一下。</p>
<p>ILSpy 打开 System.ComponentModel.Annotations.dll,确实可以看到默认的资源 FxResources.System.ComponentModel.Annotations.SR.resources,证明我们的分析没错。</p>
<p><img src="https://img2024.cnblogs.com/blog/777224/202503/777224-20250321165721826-1082022952.png"></p>
<p> </p>
<p>默认(中立语言)资源里面包含了错误消息,也包含了内部的异常消息。我们可以全部或者选择地本地化它们。</p>
<p><img src="https://img2024.cnblogs.com/blog/777224/202503/777224-20250321170002718-864052792.png"></p>
<p> </p>
<p> <span style="font-size: 18px"><strong>建立语言扩展包</strong></span></p>
<p>我们建立一个项目,名为 FxResources.System.ComponentModel.Annotations。根据默认规则,在项目中建立的资源会自动添加命名空间作为前缀。</p>
<p>因此我们只需要再创建名为 SR 的资源即可。</p>
<p><img src="https://img2024.cnblogs.com/blog/777224/202503/777224-20250321170730656-837239591.png"></p>
<p> 如图,我们建立了对应的<strong>中文简体</strong>和<strong>中文繁体</strong>资源,这样就大功告成了!</p>
<p><img src="https://img2024.cnblogs.com/blog/777224/202503/777224-20250321170935356-1542956857.png"></p>
<p><span style="color: rgba(136, 136, 136, 1)"><em>说明:zh-Hans 兼容 zh-CN、zh-SG;zh-Hant 兼容 zh-TW、zh-MO、zh-HK。严格讲港澳台繁体略有差异,但在一般场景可以忽略。</em></span></p>
<p><span style="font-size: 18px"><strong> 最终效果</strong></span></p>
<p>同样是之前的例子,我们不需要再指定 ErrorMessage。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UserDto
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name{<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">;}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span>? Age {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>现在我们得到的消息是这样,看起来还不错。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Name 字段为必填项。</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Age 字段为必填项。</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p><span style="color: rgba(128, 128, 128, 1)"><em>注意:如果你的项目没有启用国际化功能,你需要设置默认的文化为中文:CultureInfo.DefaultThreadUICulture = CultureInfo.GetCultureInfo("zh-Hans")</em></span></p>
<p><span style="font-size: 18px"><strong>Nuget 包</strong></span></p>
<p>为方便大家使用,已经将语言资源打包为语言包,大家直接安装到项目即可。</p>
<div class="cnblogs_code">
<pre>Install-Package FxResources.System.ComponentModel.Annotations.zh-Hans -Version <span style="color: rgba(128, 0, 128, 1)">9.0</span>.<span style="color: rgba(128, 0, 128, 1)">0</span></pre>
</div>
<p>.NET 不同版本的资源之间有略微差异,大家选择对应的版本安装即可。</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/himax/p/18785387/how_to_localize_validation_attrbuite_message
頁:
[1]