ABP中的Filter(下)


  接着上面的一個部分來敘述,這一篇我們來重點看ABP中的AbpUowActionFilter、AbpExceptionFilter、AbpResultFilter這三個部分也是按照之前的思路來一個個介紹,當然這里面如果和前面的Interceptor有重復的部分,那么將會對兩者進行一個對比並作出相關的說明,那么我們現在來一步步來分析這幾個Filter的細節。

  四   AbpUowActionFilter

  這個我們需要和之前的UnitOfWorkInterceptor中的上篇下篇來進行對比,工作單元部分是整個ABP中非常重要的一個部分,這里我們也來簡要的進行分析,詳細的過程可以參考UnitOfWorkInterceptor中的過程來說明,這里主要是說一下兩者的不同之處,這里我們先看看這部分的代碼,然后再進行分析。

public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency
    {
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IAbpAspNetCoreConfiguration _aspnetCoreConfiguration;
        private readonly IUnitOfWorkDefaultOptions _unitOfWorkDefaultOptions;

        public AbpUowActionFilter(
            IUnitOfWorkManager unitOfWorkManager,
            IAbpAspNetCoreConfiguration aspnetCoreConfiguration,
            IUnitOfWorkDefaultOptions unitOfWorkDefaultOptions)
        {
            _unitOfWorkManager = unitOfWorkManager;
            _aspnetCoreConfiguration = aspnetCoreConfiguration;
            _unitOfWorkDefaultOptions = unitOfWorkDefaultOptions;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            if (!context.ActionDescriptor.IsControllerAction())
            {
                await next();
                return;
            }

            var unitOfWorkAttr = _unitOfWorkDefaultOptions
                .GetUnitOfWorkAttributeOrNull(context.ActionDescriptor.GetMethodInfo()) ??
                _aspnetCoreConfiguration.DefaultUnitOfWorkAttribute;

            if (unitOfWorkAttr.IsDisabled)
            {
                await next();
                return;
            }

            using (var uow = _unitOfWorkManager.Begin(unitOfWorkAttr.CreateOptions()))
            {
                var result = await next();
                if (result.Exception == null || result.ExceptionHandled)
                {
                    await uow.CompleteAsync();
                }
            }
        }
    }

  這里我們來一步步進行分析,首先第一步也是判斷當前執行的方法是否是ControllerAction,確確來說就是當前執行的方法是否位於Controller的內部,如果當前方法不是ControllerAction的話那么就不再攔截當前方法,這個和前面分析的是一樣的。然后第二部就是通過下面的GetUnitOfWorkAttributeOrNull這個方法來判斷能夠進行后續操作。

 internal static class UnitOfWorkDefaultOptionsExtensions
    {
        public static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(this IUnitOfWorkDefaultOptions unitOfWorkDefaultOptions, MethodInfo methodInfo)
        {
            var attrs = methodInfo.GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
            if (attrs.Length > 0)
            {
                return attrs[0];
            }

            attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
            if (attrs.Length > 0)
            {
                return attrs[0];
            }

            if (unitOfWorkDefaultOptions.IsConventionalUowClass(methodInfo.DeclaringType))
            {
                return new UnitOfWorkAttribute(); //Default
            }

            return null;
        }

        public static bool IsConventionalUowClass(this IUnitOfWorkDefaultOptions unitOfWorkDefaultOptions, Type type)
        {
            return unitOfWorkDefaultOptions.ConventionalUowSelectors.Any(selector => selector(type));
        }
    }

  這是一個擴展方法,主要是獲取當前方法的名稱為UnitOfWork的自定義屬性,當然這個【UnitOfWork】可以定義在當前方法的上面,當然也可以定義在該方法的類的上面,如果定義在類的上面則表示整個類都擁有UnitOfWork特性。另外我們可以定義我們自己的規則,從而確保當前方法能夠運用到UnitOfWork的特性,具體的方法就是在UnitOfWorkDefaultOptions中定義自己的ConventionalUowSelectors,我們來看看UnitOfWorkDefaultOptions的實現。

internal class UnitOfWorkDefaultOptions : IUnitOfWorkDefaultOptions
    {
        public TransactionScopeOption Scope { get; set; }

        /// <inheritdoc/>
        public bool IsTransactional { get; set; }

        /// <inheritdoc/>
        public TimeSpan? Timeout { get; set; }

        /// <inheritdoc/>
        public bool IsTransactionScopeAvailable { get; set; }

        /// <inheritdoc/>
        public IsolationLevel? IsolationLevel { get; set; }

        public IReadOnlyList<DataFilterConfiguration> Filters => _filters;
        private readonly List<DataFilterConfiguration> _filters;

        public List<Func<Type, bool>> ConventionalUowSelectors { get; }

        public UnitOfWorkDefaultOptions()
        {
            _filters = new List<DataFilterConfiguration>();
            IsTransactional = true;
            Scope = TransactionScopeOption.Required;

            IsTransactionScopeAvailable = true;

            ConventionalUowSelectors = new List<Func<Type, bool>>
            {
                type => typeof(IRepository).IsAssignableFrom(type) ||
                        typeof(IApplicationService).IsAssignableFrom(type)
            };
        }

        public void RegisterFilter(string filterName, bool isEnabledByDefault)
        {
            if (_filters.Any(f => f.FilterName == filterName))
            {
                throw new AbpException("There is already a filter with name: " + filterName);
            }

            _filters.Add(new DataFilterConfiguration(filterName, isEnabledByDefault));
        }

        public void OverrideFilter(string filterName, bool isEnabledByDefault)
        {
            _filters.RemoveAll(f => f.FilterName == filterName);
            _filters.Add(new DataFilterConfiguration(filterName, isEnabledByDefault));
        }
    }

  在這個里面有一個公共的名稱為ConventionalUowSelectors的Func委托集合,在這個里面我們默認添加了從IRepository或者IApplicationService的類型自動添加UnitOfWork的特性的方式,當然我們也可以在我們的Module里面添加自己的UowSelector,通過上面的代碼我們可以了解整個過程,在獲取了當前的方法的UnitOfWorkAttribute后我們需要判斷當前的IsDisable是否為true,如果為true那么再次跳過UnitOfWork的過程,最后就是通過UnitOfWorkManager來啟動工作單元,其內部具體的執行過程請參考UnitOfWorkInterceptor中詳細的過程。

   五   AbpExceptionFilter

  這個應該在ABP中非常常見的一類Filter,在我們的代碼中底層拋出異常之后我們到底該怎么處理呢?我們來看看ABP中寫了哪些?處理過程又是什么樣的?

 public class AbpExceptionFilter : IExceptionFilter, ITransientDependency
    {
        public ILogger Logger { get; set; }

        public IEventBus EventBus { get; set; }

        private readonly IErrorInfoBuilder _errorInfoBuilder;
        private readonly IAbpAspNetCoreConfiguration _configuration;

        public AbpExceptionFilter(IErrorInfoBuilder errorInfoBuilder, IAbpAspNetCoreConfiguration configuration)
        {
            _errorInfoBuilder = errorInfoBuilder;
            _configuration = configuration;

            Logger = NullLogger.Instance;
            EventBus = NullEventBus.Instance;
        }

        public void OnException(ExceptionContext context)
        {
            if (!context.ActionDescriptor.IsControllerAction())
            {
                return;
            }

            var wrapResultAttribute =
                ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
                    context.ActionDescriptor.GetMethodInfo(),
                    _configuration.DefaultWrapResultAttribute
                );

            if (wrapResultAttribute.LogError)
            {
                LogHelper.LogException(Logger, context.Exception);
            }

            if (wrapResultAttribute.WrapOnError)
            {
                HandleAndWrapException(context);
            }
        }

        private void HandleAndWrapException(ExceptionContext context)
        {
            if (!ActionResultHelper.IsObjectResult(context.ActionDescriptor.GetMethodInfo().ReturnType))
            {
                return;
            }

            context.HttpContext.Response.StatusCode = GetStatusCode(context);

            context.Result = new ObjectResult(
                new AjaxResponse(
                    _errorInfoBuilder.BuildForException(context.Exception),
                    context.Exception is AbpAuthorizationException
                )
            );

            EventBus.Trigger(this, new AbpHandledExceptionData(context.Exception));

            context.Exception = null; //Handled!
        }

        protected virtual int GetStatusCode(ExceptionContext context)
        {
            if (context.Exception is AbpAuthorizationException)
            {
                return context.HttpContext.User.Identity.IsAuthenticated
                    ? (int)HttpStatusCode.Forbidden
                    : (int)HttpStatusCode.Unauthorized;
            }

            if (context.Exception is AbpValidationException)
            {
                return (int)HttpStatusCode.BadRequest;
            }

            if (context.Exception is EntityNotFoundException)
            {
                return (int)HttpStatusCode.NotFound;
            }

            return (int)HttpStatusCode.InternalServerError;
        }
    }

  在這個方法中,首先也是過濾ControllerAction,然后就會獲取當前執行方法或者其所屬的類上面是否定義了WrapResultAttribute,如果找不到自定義的WrapResultAttribute,那么會為其添加一個默認的WrapResultAttribute,默認的WrapResultAttribute中默認定義LogError=true,所以默認會通過LogHelper.LogException(Logger, context.Exception)來記錄當前系統中異常信息作為日志文件。當然這里我們也可以看看HandleAndWrapException中到底做了些什么?

  首先是判斷當前的方法的返回值是否是一個ObjectResult,那么到底什么是ObjectResult,我們一起來看看。

 public static bool IsObjectResult(Type returnType)
        {
            //Get the actual return type (unwrap Task)
            if (returnType == typeof(Task))
            {
                returnType = typeof(void);
            }
            else if (returnType.GetTypeInfo().IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
            {
                returnType = returnType.GenericTypeArguments[0];
            }

            if (typeof(IActionResult).GetTypeInfo().IsAssignableFrom(returnType))
            {
                if (typeof(JsonResult).GetTypeInfo().IsAssignableFrom(returnType) || typeof(ObjectResult).GetTypeInfo().IsAssignableFrom(returnType))
                {
                    return true;
                }

                return false;
            }

            return true;
        }

  首先來判斷當前方法的返回值是否繼承自IActionResult,在滿足這個條件以后再來看當前方法的返回值是否繼承自JsonResult或者是ObjectResult,如果是那么就返回true。返回true后我們會獲取當前Response的狀態碼並且以AjaxRespone的形式返回。這里我們看看在實際的業務中我們的處理方式,看下面的代碼。

 /// <summary>
    /// API 的未捕捉異常處理
    /// </summary>
    public class ApiExceptionFilter : IExceptionFilter {

        /// <summary>
        /// 僅針對 /api/ 開頭的 HTTP API 處理異常
        /// </summary>
        /// <param name="context">異常的上下文</param>
        public void OnException(ExceptionContext context) {
            var route = context.ActionDescriptor.AttributeRouteInfo.Template;
            if (route.StartsWith("api/")) {
                HandleException(context);
            }
        }

        /// <summary>
        /// 針對不同的異常,HTTP Response 使用不同的 Status Code, Body 均定義為 { "message": "exception message" }
        /// </summary>
        /// <param name="context"></param>
        private void HandleException(ExceptionContext context) {
            context.HttpContext.Response.StatusCode = GetStatusCode(context);

            if (context.Exception is AbpValidationException exception) {
                context.Result = new ObjectResult(
                    new {
                        // Message = "你的請求無效",
                        ValidationErrors = GetValidationErrorInfos(exception),
                        Message = GetValidationErrorNarrative(exception)
                    }
                );
            } else if (context.Exception is FileValidationException fileValidationException) {
                context.Result = new ObjectResult(
                    new {
                        payload = fileValidationException.FileName,
                        fileValidationException.Message
                    }
                );
            } else {
                var message = context.Exception.Message;
                if (context.Exception.InnerException is ValidationException)
                    message = context.Exception.InnerException.Message;
                context.Result = new ObjectResult(new {
                    Message = message
                });
            }

            context.ExceptionHandled = true;
        }

        private int GetStatusCode(ExceptionContext context) {
            if (context.Exception is AbpAuthorizationException) {
                return context.HttpContext.User.Identity.IsAuthenticated
                    ? StatusCodes.Status403Forbidden
                    : StatusCodes.Status401Unauthorized;
            }

            if (context.Exception is AbpValidationException
                || context.Exception is UserFriendlyException
                || context.Exception is ValidationException
                || context.Exception is FileValidationException
                || context.Exception.InnerException is ValidationException) {
                return StatusCodes.Status400BadRequest;
            }

            if (context.Exception is EntityNotFoundException) {
                return StatusCodes.Status404NotFound;
            }
            if (context.Exception is PreconditionRequiredException) {
                return StatusCodes.Status428PreconditionRequired;
            }

            if (context.Exception is PreconditionFailedException) {
                return StatusCodes.Status412PreconditionFailed;
            }

            return StatusCodes.Status500InternalServerError;
        }

        private ValidationErrorInfo[] GetValidationErrorInfos(AbpValidationException validationException) {
            var validationErrorInfos = new List<ValidationErrorInfo>();

            foreach (var validationResult in validationException.ValidationErrors) {
                var validationError = new ValidationErrorInfo(validationResult.ErrorMessage);

                if (validationResult.MemberNames != null && validationResult.MemberNames.Any()) {
                    validationError.Members = validationResult.MemberNames.Select(m => m.ToCamelCase()).ToArray();
                }

                validationErrorInfos.Add(validationError);
            }

            return validationErrorInfos.ToArray();
        }

        private string GetValidationErrorNarrative(AbpValidationException validationException) {
            var detailBuilder = new StringBuilder();
            detailBuilder.AppendLine("驗證過程中檢測到以下錯誤");

            foreach (var validationResult in validationException.ValidationErrors) {
                detailBuilder.AppendFormat(" - {0}", validationResult.ErrorMessage);
                detailBuilder.AppendLine();
            }

            return detailBuilder.ToString();
        }
    }

  在實際的業務過程中我們會將當前的報錯信息已一定的結構返回給調用的前端,讓前端去處理具體的異常信息,通常會將錯誤信息顯示在界面上方幾秒中,然后退出的方式。

   六   AbpResultFilter

  這個在實際過程中用的不是很多我們也來看看到底會做些什么吧?

 public class AbpResultFilter : IResultFilter, ITransientDependency
    {
        private readonly IAbpAspNetCoreConfiguration _configuration;
        private readonly IAbpActionResultWrapperFactory _actionResultWrapperFactory;

        public AbpResultFilter(IAbpAspNetCoreConfiguration configuration, 
            IAbpActionResultWrapperFactory actionResultWrapper)
        {
            _configuration = configuration;
            _actionResultWrapperFactory = actionResultWrapper;
        }

        public virtual void OnResultExecuting(ResultExecutingContext context)
        {
            if (!context.ActionDescriptor.IsControllerAction())
            {
                return;
            }

            var methodInfo = context.ActionDescriptor.GetMethodInfo();

            //var clientCacheAttribute = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
            //    methodInfo,
            //    _configuration.DefaultClientCacheAttribute
            //);

            //clientCacheAttribute?.Apply(context);
            
            var wrapResultAttribute =
                ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
                    methodInfo,
                    _configuration.DefaultWrapResultAttribute
                );

            if (!wrapResultAttribute.WrapOnSuccess)
            {
                return;
            }

            _actionResultWrapperFactory.CreateFor(context).Wrap(context);
        }

        public virtual void OnResultExecuted(ResultExecutedContext context)
        {
            //no action
        }
    }

  這個里面最重要就是最后一個Wrap方法,這個方法會根據返回的結果是JsonResult還是ObjectResult來做不同的處理,這里我以ObjectResult為例來進行說明。

 public class AbpObjectActionResultWrapper : IAbpActionResultWrapper
    {
        private readonly IServiceProvider _serviceProvider;

        public AbpObjectActionResultWrapper(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public void Wrap(ResultExecutingContext actionResult)
        {
            var objectResult = actionResult.Result as ObjectResult;
            if (objectResult == null)
            {
                throw new ArgumentException($"{nameof(actionResult)} should be ObjectResult!");
            }

            if (!(objectResult.Value is AjaxResponseBase))
            {
                objectResult.Value = new AjaxResponse(objectResult.Value);
                if (!objectResult.Formatters.Any(f => f is JsonOutputFormatter))
                {
                    objectResult.Formatters.Add(
                        new JsonOutputFormatter(
                            _serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value.SerializerSettings,
                            _serviceProvider.GetRequiredService<ArrayPool<char>>()
                        )
                    );
                }
            }
        }
    }

  這里面也比較簡單就是將最終的結果以Json的格式進行輸出。

  最后,點擊這里返回整個ABP系列的主目錄。

  

  


免責聲明!

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



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