• ABP中的拦截器之ValidationInterceptor(上)


      从今天这一节起就要深入到ABP中的每一个重要的知识点来一步步进行分析,在进行介绍ABP中的拦截器之前我们先要有个概念,到底什么是拦截器,在介绍这些之前,我们必须要了解AOP编程思想,这个一般翻译是面向切面编程,这个是和OOP相对的一个概念,在此之前应该先读一读其概念的介绍。这篇文章的重点是介绍ABP框架中的各种拦截器,并介绍其具体在代码中是如何起作用的。

      整个ABP框架中的拦截器开始于AbpBootstrapper的构造函数中,在这里我们创建了一个AbpBootstrapperOptions,在这里定义了一个DisableAllInterceptors属性,这个用来禁用添加到ABP系统中所有的所有的拦截器,默认值为false,即默认会开启所有的拦截器。当然这个参数可以在AddAbp方法中通过回调函数来进行修改。我们来看看代码是怎样添加拦截器的,这里从AddInterceptorRegistrars方法来时说起,我们来看看这个方法中都添加了哪些ABP拦截器。

     private void AddInterceptorRegistrars()
            {
                ValidationInterceptorRegistrar.Initialize(IocManager);
                AuditingInterceptorRegistrar.Initialize(IocManager);
                EntityHistoryInterceptorRegistrar.Initialize(IocManager);
                UnitOfWorkRegistrar.Initialize(IocManager);
                AuthorizationInterceptorRegistrar.Initialize(IocManager);
            }
    

      在这个方法中进行了总共进行了5中类型的拦截器的初始化,后面我们来就每一种拦截器进行说明和分析。  

      这个顾名思义是用作验证的,那么到底验证些什么东西呢?我们来一步步进行分析。

    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)
            {
                if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(handler.ComponentModel.Implementation))
                {
                    handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(ValidationInterceptor)));
                }
            }
        }
    

      首先在执行静态Initialize方法,在这个方法内部,首先将整个ABP中唯一的IoCManager作为参数传递到里面,然后订阅依赖注入容器的ComponentRegister事件,这里订阅的函数有两个参数,一个是key,另外一个是IHandle的接口,这段代码意思是说当Ioc中有组件被注册的时候(也就是往Ioc添加某个类型的时候), 检测该对象是否是IApplicationService(也就是只验证ApplicationService层), 是的话做Validation的拦截,可以做到拦截之后对ApplicationService层的方法参数做检测, Interceptors是一个拦截器集合, 可以加入更多的拦截器,在这里我们添加了一个ValidationInterceptor。所以当一个请求进入ApplicationService层之后,第一个做的事情就是 Validation,那么这个Validation到底做些什么呢?接下来我们来一步步进行分析。

     public class ValidationInterceptor : IInterceptor
        {
            private readonly IIocResolver _iocResolver;
    
            public ValidationInterceptor(IIocResolver iocResolver)
            {
                _iocResolver = iocResolver;
            }
    
            public void Intercept(IInvocation invocation)
            {
                if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Validation))
                {
                    invocation.Proceed();
                    return;
                }
    
                using (var validator = _iocResolver.ResolveAsDisposable<MethodInvocationValidator>())
                {
                    validator.Object.Initialize(invocation.MethodInvocationTarget, invocation.Arguments);
                    validator.Object.Validate();
                }
                
                invocation.Proceed();
            }
        }
    

      当我们添加了特定类型的拦截器后,当我们请求从IApplicationService类中继承的方法的时候,首先调用的是IInterceptor接口中定义的Intercept拦截方法,在这个方法中首先调用一个静态的IsApplied方法,我们来看看这个方法中到底是如何进行定义的,我们来看看这个方法内如做了什么?这个方法会传递两个参数,一个是invocation.InvocationTarget,这个指的是什么呢?这个就是当前当前继承自IApplicationService接口的自定义应用层service,第二个参数是一个静态常量,这个定义在internal static class AbpCrossCuttingConcerns这个静态类中,那么具体的形式是怎么样的呢?public const string Validation = "AbpValidation",其实就是一个字符串形式。

     public static bool IsApplied([NotNull] object obj, [NotNull] string concern)
            {
                if (obj == null)
                {
                    throw new ArgumentNullException(nameof(obj));
                }
    
                if (concern == null)
                {
                    throw new ArgumentNullException(nameof(concern));
                }
    
                return (obj as IAvoidDuplicateCrossCuttingConcerns)?.AppliedCrossCuttingConcerns.Contains(concern) ?? false;
            }

      在这里有一个IAvoidDuplicateCrossCuttingConcerns 接口,通过查阅相关的代码我们发现 ApplicationService : AbpServiceBase, IApplicationService, IAvoidDuplicateCrossCuttingConcerns ,ApplicationService刚好是继承自当前接口的,这个接口内部定义了一个AppiedCrossCuttingConcerns的List<string>的对象,那么通过上面的一番解释你应该了解了,我们自定义的应用服务层一般都是继承自ApplicationService的,当我们调用应用层的某个方法时,首先就会判断当前应用服务层中的List<string>对象中是否包含了这个常量AbpValidation,如果已经包含了那么就不执行拦截过程,就让请求的方法继续执行,如果没有包含该字符串,那么就会执行后面的拦截过程,拦截的过程就是创建一个MethodInvocationValidator对象,然后调用这个对象中的Initialize和Validate方法,这个是后面要重点讲述的过程,这里我们先跳转到另外一个话题,这里为什么需要判断这个AppiedCrossCuttingConcerns中是否包含了AbpValidation呢?难道是别的地方也会往这个集合中添加AbpValidation这个字符串吗?答案是肯定的,在我们的ABP项目中还真有另外的地方往这个集合中添加了这个字符串,那么到底是哪里呢?通过分析我们发现在ABP中还有另外一类重要的内容,他们就是Filter,这个是Asp.Net Core中的内容,那么我们的ABP中又是在何时使用到这些Filter的呢?让我们带着这些疑问来一步步去分析,首先来看看这个类。

     internal static class AbpMvcOptionsExtensions
        {
            public static void AddAbp(this MvcOptions options, IServiceCollection services)
            {
                AddConventions(options, services);
                AddFilters(options);
                AddModelBinders(options);
            }
    
            private static void AddConventions(MvcOptions options, IServiceCollection services)
            {
                options.Conventions.Add(new AbpAppServiceConvention(services));
            }
    
            private static void AddFilters(MvcOptions options)
            {
                options.Filters.AddService(typeof(AbpAuthorizationFilter));
                options.Filters.AddService(typeof(AbpAuditActionFilter));
                options.Filters.AddService(typeof(AbpValidationActionFilter));
                options.Filters.AddService(typeof(AbpUowActionFilter));
                options.Filters.AddService(typeof(AbpExceptionFilter));
                options.Filters.AddService(typeof(AbpResultFilter));
            }
    
            private static void AddModelBinders(MvcOptions options)
            {
                options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider());
            }
        }

      在这个静态类中有一个AddFilters的方法,它会MvcOptions中的Filters添加6种类型的Filter,那么这个类中的AddAbp方法是在什么时候调用的呢?这个在看过我之前系列的应该不会陌生,其实在最开始Startup类中ConfigureServices中执行AddAbp方法中就会执行配置ASPNetCore中就会调用这个方法,我们来贴出这个类中的这部分代码。

        public static class AbpServiceCollectionExtensions
        {
            /// <summary>
            /// Integrates ABP to AspNet Core.
            /// </summary>
            /// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
            /// <param name="services">Services.</param>
            /// <param name="optionsAction">An action to get/modify options</param>
            public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
                where TStartupModule : AbpModule
            {
                var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);
    
                ConfigureAspNetCore(services, abpBootstrapper.IocManager);
    
                return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
            }
    
            private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
            {
                //See https://github.com/aspnet/Mvc/issues/3936 to know why we added these services.
                services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
                services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
                
                //Use DI to create controllers
                services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
    
                //Use DI to create view components
                services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());
    
                //Change anti forgery filters (to work proper with non-browser clients)
                services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AbpAutoValidateAntiforgeryTokenAuthorizationFilter>());
                services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, AbpValidateAntiforgeryTokenAuthorizationFilter>());
    
                //Add feature providers
                var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
                partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver));
    
                //Configure JSON serializer
                services.Configure<MvcJsonOptions>(jsonOptions =>
                {
                    jsonOptions.SerializerSettings.ContractResolver = new AbpContractResolver
                    {
                        NamingStrategy = new CamelCaseNamingStrategy()
                    };
                });
    
                //Configure MVC
                services.Configure<MvcOptions>(mvcOptions =>
                {
                    mvcOptions.AddAbp(services);
                });
    
                //Configure Razor
                services.Insert(0,
                    ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
                        new ConfigureOptions<RazorViewEngineOptions>(
                            (options) =>
                            {
                                options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver));
                            }
                        )
                    )
                );
            }
    
            private static AbpBootstrapper AddAbpBootstrapper<TStartupModule>(IServiceCollection services, Action<AbpBootstrapperOptions> optionsAction)
                where TStartupModule : AbpModule
            {
                var abpBootstrapper = AbpBootstrapper.Create<TStartupModule>(optionsAction);
                services.AddSingleton(abpBootstrapper);
                return abpBootstrapper;
            }
        }
    

      请看上面这个类中的//Configure MVC这段注释的代码,在这段代码中会调用AbpMvcOptionsExtensions中的静态AddAbp方法,从而为整个ABP项目配置Filter,这个我们在后面的文章也会写一个系列来介绍ABP中的Filter,今天这篇文章的重点不是这个内容,这里介绍这部分内容只是为了和ABP中的Interceptor来进行对比,从而说明ABP中的拦截器的作用。

      在了解了这些Filter是如何添加到ABP系统后,那么就让我们来重点关注AbpValidationActionFilter这个类型的Filter。

    public class AbpValidationActionFilter : IAsyncActionFilter, ITransientDependency
        {
            private readonly IIocResolver _iocResolver;
            private readonly IAbpAspNetCoreConfiguration _configuration;
    
            public AbpValidationActionFilter(IIocResolver iocResolver, IAbpAspNetCoreConfiguration configuration)
            {
                _iocResolver = iocResolver;
                _configuration = configuration;
            }
    
            public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
            {
                if (!_configuration.IsValidationEnabledForControllers || !context.ActionDescriptor.IsControllerAction())
                {
                    await next();
                    return;
                }
    
                using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Validation))
                {
                    using (var validator = _iocResolver.ResolveAsDisposable<MvcActionInvocationValidator>())
                    {
                        validator.Object.Initialize(context);
                        validator.Object.Validate();
                    }
    
                    await next();
                }
            }
        }
    

      熟悉Asp.Net Core的都应该了解AspNet Core中的管道的执行概念,如果对ABP中的Filter还不是很了解的情况下请先读一下下面的这篇文章,从而让自己对整个概念有一个更加清晰的认识。总之ABP中我们添加的AbpValidationActionFilter 会先于定义的拦截器中的方法,当执行一个继承自ApplicationService中的方法的时候,首先会被上面定义的Filter拦截,然后调用OnActionExecutionAsync这个方法,我们来看看这个方法中到底做了些什么,在这个方法中首先会判断IsValidationEnabledForControllers这个ABP配置项中的配置参数,这个参数默认配置为ture,第二部分的判断当前执行方法是否是控制器Action,如果是的话就继续验证后面的过程,所以到了这里我们会发现AbpValidationActionFilter和ValidationInterceptor作用的范围是不同的,前者主要作用于Controller层而后者主要是作用于ABP中的应用层,所以两者的同时存在是有必要的,接下来OnActionExecutionAsync方法会执行下面的Using包含的方法AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Validation)在这个方法中会执行静态类AbpCrossCuttingConcerns中的静态Applying方法,如果当前执行的方法所属的Controller继承自IAvoidDuplicateCrossCuttingConcerns接口,那么这个方法会将字符串常量AbpValidation添加到当前继承自ApplicationService中的应用服务类的List<string>类型的AppiedCrossCuttingConcerns变量中,如果满足上面的这些条件,那么在ABP中如果执行了ValidateFilter后,后面的ValidationInterceptor中的方法是不会执行的,但是在我们的项目中几乎没有任何Controller会继承自这个接口(IAvoidDuplicateCrossCuttingConcerns),所以两者在ABP系统中是彼此独立存在的。所以这里我们重点讲述一下,这个Filter后面执行的方法,var validator = _iocResolver.ResolveAsDisposable<MvcActionInvocationValidator>(),这个方法利用依赖注入的iocResolver对象来创建一个MvcActionInvocationValidator的实例,我们会发现这个类最终是继承自MethodInvocationValidator,所以后面执行Intialize方法和Validate方法,执行的实际上就是MethodInvocationValidator中的方法,这个最终和AbpValidationInterceptor中执行是一致的,通过这些分析你应该对整个ABP中的验证Validation有一个清晰的认识了,当然想要完全了解这个过程,最好的方式还是去下载ABP的源代码然后一步步去分析。

      在后面我们的重点是分析这个MethodInvocationValidator这个方法,然后看看这里面到底做了些什么工作,我们通过代码来分析这两个过程。首先便是Initialize方法,我们来看看这个里面都执行了哪些操作。

    /// <param name="method">Method to be validated</param>
            /// <param name="parameterValues">List of arguments those are used to call the <paramref name="method"/>.</param>
            public virtual void Initialize(MethodInfo method, object[] parameterValues)
            {
                Check.NotNull(method, nameof(method));
                Check.NotNull(parameterValues, nameof(parameterValues));
                Method = method;
                ParameterValues = parameterValues;
                Parameters = method.GetParameters();
            }
    

      这个其实一看就知道,那就是获取当前继承自ApplicationService的类中的 请求方法,并获取相应的参数,并将这些方法放到当前类中的私有全局变量中去,在做好了这些准备工作后就只进行方法的验证工作了,我们来看看究竟验证了哪些东西,有哪些验证的规则。

    /// <summary>
            /// Validates the method invocation.
            /// </summary>
            public void Validate()
            {
                CheckInitialized();
    
                if (Parameters.IsNullOrEmpty())
                {
                    return;
                }
    
                if (!Method.IsPublic)
                {
                    return;
                }
    
                if (IsValidationDisabled())
                {
                    return;                
                }
    
                if (Parameters.Length != ParameterValues.Length)
                {
                    throw new Exception("Method parameter count does not match with argument count!");
                }
    
                for (var i = 0; i < Parameters.Length; i++)
                {
                    ValidateMethodParameter(Parameters[i], ParameterValues[i]);
                }
    
                if (ValidationErrors.Any())
                {
                    ThrowValidationError();
                }
    
                foreach (var objectToBeNormalized in ObjectsToBeNormalized)
                {
                    objectToBeNormalized.Normalize();
                }
            }
    

      在这个方法中首先会进行一些排除工作,首先该方法必须是公共方法、参数不能为null、而且没有ValidationDisabled,并且方法的参数要和通过反射获取到的保持一致,在做完这些基础的工作后就会对每一个方法的参数进行验证,这里有一个ValidateMethodParameter的方法来验证每一个参数,我们来看看这个方法中做了哪些工作,也是和上面一样的,我们来看看代码。

    /// <summary>
            /// Validates given parameter for given value.
            /// </summary>
            /// <param name="parameterInfo">Parameter of the method to validate</param>
            /// <param name="parameterValue">Value to validate</param>
            protected virtual void ValidateMethodParameter(ParameterInfo parameterInfo, object parameterValue)
            {
                if (parameterValue == null)
                {
                    if (!parameterInfo.IsOptional && 
                        !parameterInfo.IsOut && 
                        !TypeHelper.IsPrimitiveExtendedIncludingNullable(parameterInfo.ParameterType, includeEnums: true))
                    {
                        ValidationErrors.Add(new ValidationResult(parameterInfo.Name + " is null!", new[] { parameterInfo.Name }));
                    }
    
                    return;
                }
    
                ValidateObjectRecursively(parameterValue, 1);
            }
    

      这里面主要是判断当前的参数是否包含一些特殊的情况,比如是否定义了Out等,这里便不再赘述,重点来看看其中调用的子函数ValidateObjectRecursively,我们也来看看这个到底做了些什么?

     protected virtual void ValidateObjectRecursively(object validatingObject, int currentDepth)
            {
                if (currentDepth > MaxRecursiveParameterValidationDepth)
                {
                    return;
                }
    
                if (validatingObject == null)
                {
                    return;
                }
    
                if (_configuration.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject)))
                {
                    return;
                }
    
                if (TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObject.GetType()))
                {
                    return;
                }
    
                SetValidationErrors(validatingObject);
    
                // Validate items of enumerable
                if (IsEnumerable(validatingObject))
                {
                    foreach (var item in (IEnumerable) validatingObject)
                    {
                        ValidateObjectRecursively(item, currentDepth + 1);
                    }
                }
    
                // Add list to be normalized later
                if (validatingObject is IShouldNormalize)
                {
                    ObjectsToBeNormalized.Add(validatingObject as IShouldNormalize);
                }
    
                if (ShouldMakeDeepValidation(validatingObject))
                {
                    var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
                    foreach (var property in properties)
                    {
                        if (property.Attributes.OfType<DisableValidationAttribute>().Any())
                        {
                            continue;
                        }
    
                        ValidateObjectRecursively(property.GetValue(validatingObject), currentDepth + 1);
                    }
                }
            }
    

      在这个方法中首先为一个对象定义了一个最大的嵌套验证层级为8,如果当前验证层级超过了8就直接返回,后面紧接着判断当前验证的方法的参数是否为null,如果为null那么也直接返回,紧接着判断当前参数类型是否是ABP中默认配置的忽略的类型,如果是的话也直接返回,接下来判断该参数是否是C#中定义的基础类型Primitive Types,The primitive types are BooleanByteSByteInt16UInt16Int32UInt32Int64UInt64IntPtrUIntPtrCharDouble, and Single.这些类型,另外还讲忽略掉一些常见string、Guido、TimeSpan、DateTime这些类型将直接被忽略掉,在排除掉这些类型以后,就是我们真正需要进行验证的类型了,后面紧接着进入了这个函数SetValidationErrors了

    protected virtual void SetValidationErrors(object validatingObject)
            {
                foreach (var validatorType in _configuration.Validators)
                {
                    if (ShouldValidateUsingValidator(validatingObject, validatorType))
                    {
                        using (var validator = _iocResolver.ResolveAsDisposable<IMethodParameterValidator>(validatorType))
                        {
                            var validationResults = validator.Object.Validate(validatingObject);
                            ValidationErrors.AddRange(validationResults);
                        }
                    }
                }
            }
    

      在这个方法中首先会调用ABP中的配置文件中有哪些Validator,这个在AbpKenelModule中默认增加了集中继承自IMethodParameterValidator的几个类型,让我们来看一看。

     private void AddMethodParameterValidators()
            {
                Configuration.Validation.Validators.Add<DataAnnotationsValidator>();
                Configuration.Validation.Validators.Add<ValidatableObjectValidator>();
                Configuration.Validation.Validators.Add<CustomValidator>();
            }
    

      关于每一种验证器,这个会在后面的文章中来逐步进行分析。这里只是做一个概述性的说明。在执行完毕ABP中的默认添加的Validatory以后,后面还有一系类的操作,再在后面会验证当前的validatingObject是否是继承自IEnumerable接口,比如当前传入的参数是List类型的数据,那么就会进行For循环再次验证里面的每一个参数,这里是通过迭代进行验证的,再在后面会判断validatingObject是否继承自IShouldNormalize接口,如果继承自该接口那么就会调用接口中的Normalize方法来对当前对象中的一些参数进行一些标准化的操作,另外最后进行的操作进行判断是否需要对当前对象进行深度验证的操作,判断的依据就是当前对象不是继承自IEnumerable接口并且也不是基础类型,这里过滤掉不是继承自IEnumerable类型是有道理的,因为在执行深度验证之前已经验证过了这个情况避免进行了重复的验证过程,为什么要再做深度验证当前的validatingObject这个过程呢?因为当前对象中可以包含常规的属性,但是也可以包含另外的对象,对于这种对象中又嵌套验证对象就必须通过迭代进行深度验证,但是验证的时候也不可能无限地进行验证下去,所以才有了在每一次在进行迭代之前判断当前的currentDepth > MaxRecursiveParameterValidationDepth的时候就直接退出迭代了,否则真的就会没完没了了,在ABP中MaxRecursiveParameterValidationDepth默认为8,也就是最多进行8次迭代操作过程,这个希望读者能够理解这个,在进行深度验证这个validatingObject的时候,会获取这个对象的所有属性,然后再迭代验证这些属性,然后又会重复执行上面的整个过程,这个就是整个验证的全过程,另外这篇文章没有涉及到具体的ABP中中几种Validitor,这个将会在后面一篇中具体介绍,另外在下篇中还会重点介绍这些验证器到底是怎么用的,这篇文章是按照ABP源码中的顺序进行一步步分析的,理解的时候最好是参照源代码进行一步步分析,文中可能有很多不是十分准确的地方,但是也是经过自己一点点去分析得出的结论,希望能够给新接触ABP系列的有一个指导性的参考,写得不好的望海涵。

       最后,点击这里返回整个ABP系列的主目录。

  • 相关阅读:
    插入数据失败提示: Setting autocommit to false on JDBC Connection 自动提交失败
    MyBatis XML配置properties
    mybatis 测试输出SQL语句到控制台配置
    原创:mysql5 还原至mysql 8.0.11数据库链接配置提示错误(修改内容有三处
    idea 快捷键汇总
    maven依赖配置和依赖范围
    pom.xml 配置 收藏
    单词的提取
    UVA10815 安迪的第一个字典 Andy's First Dictionary
    UVA11054 Gergovia的酒交易 Wine trading in Gergovia
  • 原文地址:https://www.cnblogs.com/seekdream/p/9557427.html
Copyright © 2020-2023  润新知