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

  

  

posted @ 2019-03-07 16:41  Hello——寻梦者!  阅读(1876)  评论(0编辑  收藏  举报