AngualrABPABP

ABP:后台验证多语言

2018-07-11  本文已影响108人  诸葛_小亮

目的

ABP已经封装好了一套DTO的验证机制,如果DTO数据不符合,ABP会自动弹窗显示错误信息。
但是这样有个限制,使用DataAnnotations特性时,后台验证返回的信息,一直是应为状态,如果修改成从多语言系统中读取呢?
例如下面的信息:


当UserName为空时,希望在英文状态下是UserName is not allow null,中文状态下用户名不能为空
当Name为空时,希望在英文状态下Name is not allow null,中文环境下是名字不能为空

实现方案一:ErrorMessageResourceTypeErrorMessageResourceName

使用ErrorMessageResourceTypeErrorMessageResourceName
这两个是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异常信息,同时我们注意到,ThrowValidationErrorvirtual方法,这为我们使用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

上一篇 下一篇

猜你喜欢

热点阅读