本地化ASP.NET Core标识验证错误信息
使用ASP.NET Core标识框架注册用户时,如果用户验证不通过,例如密码不符合安全策略、用户名重复等,UserManager的CreateAsync方法的返回值会包含一个Errors属性,这是一个IdentityError的集合,IdentityError对象包含两个属性:Code和Description,即错误代码和错误描述。把错误描述添加到模型验证错误中就能在客户端中显示验证错误信息。代码如下:
var result = await userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
return RedirectToLocal(returnUrl);
}
foreach (IdentityError error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
例如在注册用户时,输入密码123,如果没有对默认的标识进行配置,修改密码设定选项,那么提交后客户端会显示验证错误:

如果是中文网站,当然希望这里能显示中文,但这些错误信息显然是封装在标识框架中的,我们不可能去修改标识框架的源码来达到目的(虽然能够做到),那么应该怎么做呢?
看一下IdentityError的文档,发现这个类只有两个属性Code和Description,没有任何其它成员,那么出现验证错误时,IdentityError对象的Code和Description的值来自何方?我们已经知道Description的值,Code的值又是什么?
在foreach (IdentityError error in result.Errors)下个断点观察一下error对象的值,可以看到Code依次是:PasswordTooShort、PasswordRequiresNonAlphanumeric、PasswordRequiresLower和PasswordRequiresUpper,从命名上就可以看出Code和Description的对应关系了。
不过得知Code的值还是没什么帮助,继续看文档,发现下面有一个IdentityErrorDescriber类,看名字就应该猜到和验证错误的描述有关,类的说明只有一行字:允许为显示给应用程序的标识错误信息本地化的服务。这句话说得太明确了,再看看类的成员,没有属性,都是方法,方法和IdentityError的Code值一样,而且这些方法全是虚方法!很显然,想要本地化错误信息,应该继承这个类,并重写相应方法。
找到本地化的方式,那么如何让应用程序从我们继承的类中获得错误信息呢?这个也不用担心,ASP.NET Core内置的依赖注入可以让我们轻松地用一个类来替换另一个类。
在Startup类的ConfigureServices方法中添加一行代码:
services.AddTransient(typeof(IdentityErrorDescriber), typeof(LocalizedIdentityErrorDescriber));
这行代码的意思是用LocalizedIdentityErrorDescriber类来替换IdentityErrorDescriber类,应用程序对IdentityErrorDescriber的引用全部会转向LocalizedIdentityErrorDescriber。
在项目中添加一个类:LocalizedIdentityErrorDescriber,继承自IdentityErrorDescriber,并重写相应方法。
public class LocalizedIdentityErrorDescriber: IdentityErrorDescriber
{
public override IdentityError PasswordTooShort(int length)
{
return new IdentityError
{
Code = "PasswordTooShort",
Description = $"密码最小长度为{length}"
};
}
//参照上述代码重写其它方法,略
}
重写原则很简单,返回一个IdentityError对象,Code属性和方法名称相同,Description则是你自定义的错误信息。
再次运行程序,输入密码123,提交后就可以看到中文验证信息了。
中文显示没问题了,但如果我们的网站是运行在多语言环境下又怎么办呢?所以最终的解决办法是要完全 本地化。
在项目中添加一个文件夹:Resources
在Resources文件夹中添加新项,选择资源文件,命名为IdentityError.resx,编辑文件内容如下:

做为示例,这里只添加了“用户名重复”,“密码太短”和“密码需要大写字符”三个验证信息。
复制并在同目录下粘贴IdentityError.resx文件,将IdentityError - 复制.resx文件重命名为IdentityError.zh-CN.resx,修改IdentityError.zh-CN.resx的内容:

重新生成项目。
修改LocalizedIdentityErrorDescriber类
public class LocalizedIdentityErrorDescriber: IdentityErrorDescriber
{
private ResourceManager GetResourceManager()
{
return new ResourceManager("WebTest.Resources.IdentityError", typeof(LocalizedIdentityErrorDescriber).Assembly);
}
public override IdentityError PasswordTooShort(int length)
{
return new IdentityError
{
Code = "PasswordTooShort",
Description = string.Format(GetResourceManager().GetString("PasswordTooShort"), length);
};
}
//参照上述代码重写其它方法,略
}
类当中添加了一个方法GetResourceManager用来生成读取资源文件的对象。在PasswordTooShort方法中不再使用硬编码为Description赋值,而是从资源文件中读取字符串。
要想支持其它语言,复制IdentityError.resx,重新命名为IdentityError.国家代码.resx,并修改资源的值就可以了。