目的
ABP已经封装好了一套DTO的验证机制,如果DTO数据不符合,ABP会自动弹窗显示错误信息。
但是这样有个限制,使用DataAnnotations
特性时,后台验证返回的信息,一直是应为状态,如果修改成从多语言系统中读取呢?
例如下面的信息:
当UserName为空时,希望在英文状态下是
UserName is not allow null
,中文状态下用户名不能为空
当Name为空时,希望在英文状态下
Name is not allow null
,中文环境下是名字不能为空
实现方案一:ErrorMessageResourceType
和 ErrorMessageResourceName
使用ErrorMessageResourceType
和 ErrorMessageResourceName
这两个是DataAnnotations
特性的属性,为了使用它们,需要自定义ErrorMessageResourceType
自定义ErrorMessageResourceType
AbpAlainResourceManager
public class AbpAlainResourceManager
{
public static string GetStringKey(string name)
{
var localizationManager = IocManager.Instance.Resolve<ILocalizationManager>();
return localizationManager.GetString(AbpAlainConsts.LocalizationSourceName, name);
}
public static string UserNameNotNull {
get
{
return GetStringKey("UserNameNotNull");
}
}
}
自定义ErrorMessageResourceType
必须提供ErrorMessageResourceName
对应名称的的静态属性,如代码中的public static string UserNameNotNull
,将静态属性的get
操作返回ABP多语言体系的内容,使用后的代码如下
Swagger调用验证
为了验证以上的内容,我们通过Swagger调用上述接口信息
调用参数 中文
缺点
使用该方法,需要针对每种语言信息编写对应的静态属性,代码繁琐。
实现方案二:高级进阶AOP
ABP的扩展性很高,除了一般的接口替换外,可扩展的另外一种途径就是AOP了,使用AOP动态切入到目标方法体内,替换自己想要的内容
ABP 如何验证 DataAnnotations
特性
通过阅读ABP源码,可查阅一下代码MvcActionInvocationValidator
ABP源码public class MvcActionInvocationValidator : ActionInvocationValidatorBase
{
protected ActionExecutingContext ActionContext { get; private set; }
public MvcActionInvocationValidator(IValidationConfiguration configuration, IIocResolver iocResolver)
: base(configuration, iocResolver)
{
}
public void Initialize(ActionExecutingContext actionContext)
{
ActionContext = actionContext;
base.Initialize(actionContext.ActionDescriptor.GetMethodInfo());
}
protected override object GetParameterValue(string parameterName)
{
return ActionContext.ActionArguments.GetOrDefault(parameterName);
}
protected override void SetDataAnnotationAttributeErrors()
{
foreach (var state in ActionContext.ModelState)
{
foreach (var error in state.Value.Errors)
{
ValidationErrors.Add(new ValidationResult(error.ErrorMessage, new[] { state.Key }));
}
}
}
}
MvcActionInvocationValidator
类继承自ActionInvocationValidatorBase
,ActionInvocationValidatorBase
记者曾自MethodInvocationValidator
阅读MvcActionInvocationValidator
中的SetDataAnnotationAttributeErrors
方法可知是在这里进行验证 DataAnnotations
特性的。
那么又是如何处理验证不同的错误信息呢,源代码中MethodInvocationValidator
中
在第87行中,得知如果出现了错误信息,会抛出异常
87行
查阅
ThrowValidationError
方法ThrowValidationError
发现这里是抛出了
AbpValidationException
异常信息,同时我们注意到,ThrowValidationError
是virtual
方法,这为我们使用AOP提供了基础。我们使用的AOP的切入点就是这里了,在方法抛出异常信息之前,改变
ValidationErrors
的信息即可因此,我们需要编写拦截器,拦截
MvcActionInvocationValidator
类中的ThrowValidationError
方法
MvcActionInvocationValidatorInterceptor 拦截器代码
public class MvcActionInvocationValidatorInterceptor : IInterceptor
{
private readonly ILocalizationManager _localizationManager;
public MvcActionInvocationValidatorInterceptor(ILocalizationManager localizationManager)
{
this._localizationManager = localizationManager;
}
public void Intercept(IInvocation invocation)
{
var method = invocation.Method.Name;
if (method!= "ThrowValidationError")
{
invocation.Proceed();
return;
}
try
{
invocation.Proceed();
}
catch (AbpValidationException e)
{
foreach (var validationResult in e.ValidationErrors)
{
if (!validationResult.ErrorMessage.Contains("#"))
{
continue;
}
var errorStrings = validationResult.ErrorMessage.Split("#");
if (errorStrings.Length < 2)
{
continue;
}
if (errorStrings[0] != "ABP")
{
continue;
}
var key = errorStrings[1];
validationResult.ErrorMessage = this._localizationManager.GetString(
AbpAlainConsts.LocalizationSourceName,
key);
}
throw;
}
}
}
66--70
第66到70行表示我们只拦截
ThrowValidationError
方法,其他不拦截替换信息
由于原生代码,是抛出异常,所以我们也需要使用
try..catch...
,并且只捕捉:AbpValidationException
异常信息,在这里,将具体的错误给替换掉
注册拦截异常
internal static class ValidationInterceptorRegistrar
{
public static void Initialize(IIocManager iocManager)
{
iocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered;
}
private static void Kernel_ComponentRegistered(string key, IHandler handler)
{
var name = handler.ComponentModel.Implementation.Name;
if (name == "MvcActionInvocationValidator")
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MvcActionInvocationValidatorInterceptor)));
}
}
}
177
第117行表示我们只拦截
MvcActionInvocationValidatorInterceptor
类
在module中初始化拦截器
初始化拦截器
使用方式
为了能够读取到多语言信息,我们将ErrorMessage进行了特殊格式化处理,即使用ABP#
开头的,才会使用多语言替换
swagger验证
参数信息如下
参数信息
运行结果如下
运行结果
我们发现,name的验证错误信息,已经 是我们定义在资源文件中 的内容了
缺点
使用AOP唯一的缺点,就是需要添加ErrorMessage,并且ErrorMessage必须使用ABP#开头,紧跟着多语言的key
相比方案一,则少了许多静态属性的编写。
资源文件
中文英文
我的公众号
我的公众号源代码
源代码:https://github.com/ZhaoRd/abp-alain/tree/feature/ValidationLocalization
网友评论