美文网首页AngualrABPABP
ABP:后台验证多语言

ABP:后台验证多语言

作者: 诸葛_小亮 | 来源:发表于2018-07-11 21:55 被阅读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

    相关文章

      网友评论

        本文标题:ABP:后台验证多语言

        本文链接:https://www.haomeiwen.com/subject/ueekpftx.html