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系列的主目錄。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM