zoukankan      html  css  js  c++  java
  • 利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理

    在我们开发Web API应用的时候,我们可以借鉴ABP框架的过滤器Filter和特性Attribute的应用,实现对Web API返回结果的封装和统一异常处理,本篇随笔介绍利用AuthorizeAttribute实现Web API身份认证,利用ActionFilterAttribute实现对常规Web API返回结果进行统一格式的封装,利用ExceptionFilterAttribute实现对接口异常的统一处理,实现我们Web API常用到的几个通用处理过程。

    1、Asp.net的Web API过滤器介绍

    过滤器主要有这么几种:AuthorizationFilterAttribute 权限验证、ActionFilterAttribute 日志参数验证等、ExceptionFilterAttribute 异常处理捕获。

    ActionFilter 主要实现执行请求方法体之前(覆盖基类方法OnActionExecuting),和之后的事件处理(覆盖基类方法OnActionExecuted);ExceptionFilter主要实现触发异常方法(覆盖基类方法OnException)。

    ActionFilterAttrubute提供了两个方法进行拦截:

    • OnActionExecuting和OnActionExecuted,他们都提供了同步和异步的方法。
    • OnActionExecuting方法在Action执行之前执行,OnActionExecuted方法在Action执行完成之后执行。

    在使用MVC的时候,ActionFilter提供了一个Order属性,用户可以根据这个属性控制Filter的调用顺序,而Web API却不再支持该属性。Web API的Filter有自己的一套调用顺序规则:

    所有Filter根据注册位置的不同拥有三种作用域:Global、Controller、Action:

    • 通过HttpConfiguration类实例下Filters.Add()方法注册的Filter(一般在App_StartWebApiConfig.cs文件中的Register方法中设置)就属于Global作用域;

    • 通过Controller上打的Attribute进行注册的Filter就属于Controller作用域;

    • 通过Action上打的Attribute进行注册的Filter就属于Action作用域;

    他们遵循了以下规则:

    • 在同一作用域下,AuthorizationFilter最先执行,之后执行ActionFilter
    • 对于AuthorizationFilter和ActionFilter.OnActionExcuting来说,如果一个请求的生命周期中有多个Filter的话,执行顺序都是Global->Controller->Action;
    • 对于ActionFilter,OnActionExecuting总是先于OnActionExecuted执行;
    • 对于ExceptionFilter和ActionFilter.OnActionExcuted而言执行顺序为Action->Controller->Global;
    • 对于所有Filter来说,如果阻止了请求:即对Response进行了赋值,则后续的Filter不再执行。

    另外,值得注意的是,由于Web API的过滤器无法改变其顺序,那么它是按照 AuthorizationFilterAttribute -> ActionFilterAttribute -> ExceptionFilterAttribute 这个执行顺序来处理的,也就是说授权过滤器执行在前面,再次到自定义的ActionFilter,最后才是异常的过滤器处理。

    2、Web API的身份授权过滤器处理

    我们通过AuthorizationFilterAttribute 过滤器来处理用户Web API接口身份,比每次在代码上进行验证省事很多。

    一般情况下,我们只要定义类继承于AuthorizeAttribute即可,由于AuthorizeAttribute是继承于AuthorizationFilterAttribute,如下所示。

        /// <summary>
        /// 验证Web Api接口用户身份
        /// </summary>
        public class ApiAuthorizeAttribute : AuthorizeAttribute
        {
                ...........
         }

     而一般情况下,我们只需要重写bool IsAuthorized(HttpActionContext actionContext) 方法,实现授权处理逻辑即可。

     我们在CheckToken的主要逻辑里面,主要对token令牌进行反向解析,并判断用户身份是否符合,如果不符合抛出异常,就会切换到异常处理器里面了。

      然后在Web API控制器中,需要授权访问的Api控制器定义即可,我们可以把它放到基类里面声明这个过滤器特性。

     那么所有Api接口的访问,都会检查用户的身份了。

    2、自定义过滤器特性ActionFilterAttribute 的处理

    这个ActionFilterAttribute 主要用于拦截用户访问控制器方法的处理过程,前面说到,OnActionExecuting方法在Action执行之前执行,OnActionExecuted方法在Action执行完成之后执行。

    那么我们可以利用它进行函数AOP的处理了,也就是在执行前,执行后进行日志记录等,还有就是常规的参数检查、结果封装等,都可以在这个自定义过滤器中实现。

    我们定义一个类WrapResultAttribute来标记封装结果,定义一个类DontWrapResultAttribute来标记不封装结果。

        /// <summary>
        /// 用于判断Web API需要包装返回结果.
        /// </summary>
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
        public class WrapResultAttribute : ActionFilterAttribute
    {
    
    }
    
        /// <summary>
        /// 用于判断Web API不需要包装返回结果.
        /// </summary>
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
        public class DontWrapResultAttribute : WrapResultAttribute
    {
    
    }

    这个处理方式是借用ABP框架中这两个特性的处理逻辑。

    利用,对于获取用户身份令牌的基础操作接口,我们可以不封装返回结果,如下标记所示。

      那么执行后,返回的结果如下所示,就是正常的TokenResult对象的JSON信息

    {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxIiwiaWF0IjoxNjE3MjY0MDQ4LCJqdGkiOiI0NTBjZmY3OC01OTEwLTQwYzUtYmJjMC01OTQ0YzNjMjhjNTUiLCJuYW1lIjoiYWRtaW4iLCJjb3JwaWQiOiI2IiwiY2hhbm5lbCI6IjAiLCJzaGFyZWRrZXkiOiIxMjM0YWJjZCJ9.Umv4j80Sj6BnoCCGO5LrnyddwtfqU5a8Jii92SjPApw",
        "expires_in": 604800
    }

    如果取消这个DontWrapResult的标记,那么它就继承基类BaseApiController的WrapResult的标记定义了。

        /// <summary>
        /// 所有接口基类
        /// </summary>
        [ExceptionHandling]
        [WrapResult]
        public class BaseApiController : ApiController

    那么接口定义不变,但是返回的okenResult对象的JSON信息已经经过包装了。

    {
        "result": {
            "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxIiwiaWF0IjoxNjE3MjY0NDQ5LCJqdGkiOiJmZTAzYzhlNi03NGVjLTRjNmEtYmMyZi01NTU3MjFiOTM1NDEiLCJuYW1lIjoiYWRtaW4iLCJjb3JwaWQiOiI2IiwiY2hhbm5lbCI6IjAiLCJzaGFyZWRrZXkiOiIxMjM0YWJjZCJ9.9B4dyoE9YTisl36A-w_evLs2o8raopwvDUIr2LxhO1c",
            "expires_in": 604800
        },
        "targetUrl": null,
        "success": true,
        "error": null,
        "unAuthorizedRequest": false,
        "__api": true
    }

    这个JSON格式是我们一个通用的接口返回,其中Result里面定义了返回的信息,而Error里面则定义一些错误信息(如果有错误的话),而success则用于判断是否执行成功,如果有错误异常信息,那么success返回为false。

    这个通用返回的定义,是依照ABP框架的返回格式进行调整的,可以作为我们普通Web API的一个通用返回结果的处理。

    前面提到过ActionFilterAttribute自定义处理过程,在控制器方法完成后,我们对返回的结果进行进一步的封装处理即可。

    我们需要重写逻辑实现OnActionExecuted的函数

     在做包装返回结果之前,我们需要判断是否方法或者控制器设置了不包装的标记DontWrapResultAttribute。

            public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
            {
                //如果有异常,则退出交给Exception的通用处理
                if (actionExecutedContext.Exception != null)
                    return;
    
                //正常完成,那么判断是否需要包装结果输出,如果不需要则返回
                var dontWrap = false;
                var actionContext = actionExecutedContext.ActionContext;
                if (actionContext.ActionDescriptor is ReflectedHttpActionDescriptor actionDesc)
                {
                    //判断方法是否包含DontWrapResultAttribute
                    dontWrap = actionDesc.MethodInfo.GetCustomAttributes(inherit: false)
                        .Any(a => a.GetType().Equals(typeof(DontWrapResultAttribute)));
    
                    if (dontWrap) return;
                }
                if (actionContext.ControllerContext.ControllerDescriptor is HttpControllerDescriptor controllerDesc)
                {
                    //判断控制器是否包含DontWrapResultAttribute
                    dontWrap = controllerDesc.GetCustomAttributes<Attribute>(inherit: true)
                      .Any(a => a.GetType().Equals(typeof(DontWrapResultAttribute)));
    
                    if (dontWrap) return;
                }

    上述代码也就是如果找到方法或者控制器有定义DontWrapResultAttribute的,就不要包装结果,否则下一步就是对结果进行封装了

                //需要包装,那么就包装输出结果
                AjaxResponse result = new AjaxResponse();
                // 状态代码
                var statusCode = actionContext.Response.StatusCode;
                // 读取返回的内容
                var content = actionContext.Response.Content.ReadAsAsync<object>().Result;
                // 请求是否成功
                result.Success = actionContext.Response.IsSuccessStatusCode;
    
                // 重新封装回传格式
                actionExecutedContext.Response = new HttpResponseMessage(statusCode)
                {
                    Content = new ObjectContent<AjaxResponse>(
                       new AjaxResponse(content), JsonFomatterHelper.GetFormatter())
                };

    其中AjaxResponse是参考ABP框架里面返回结果的类定义处理的。

        public abstract class AjaxResponseBase
        {
            public string TargetUrl { get; set; }
    
            public bool Success { get; set; }
    
            public ErrorInfo Error { get; set; }
    
            public bool UnAuthorizedRequest { get; set; }
    
            public bool __api { get; } = true;
        }
        [Serializable]
        public class AjaxResponse<TResult> : AjaxResponseBase
        {
            public TResult Result { get; set; }
    
          }

     3、异常处理过滤器ExceptionFilterAttribute 

     前面介绍到,Web API的过滤器无法改变其顺序,它是按照 AuthorizationFilterAttribute -> ActionFilterAttribute -> ExceptionFilterAttribute 这个执行顺序来处理的,也就是说授权过滤器执行在前面,再次到自定义的ActionFilter,最后才是异常的过滤器处理。

    异常处理过滤器,我们定义后,可以统一处理和封装异常信息,而我们只需要实现OnException的方法即可。

        /// <summary>
        /// 自定义异常处理
        /// </summary>
        public class ExceptionHandlingAttribute : ExceptionFilterAttribute
        {
            /// <summary>
            /// 统一对调用异常信息进行处理,返回自定义的异常信息
            /// </summary>
            /// <param name="context">HTTP上下文对象</param>
            public override void OnException(HttpActionExecutedContext context)
            {
            }
        }

    完整的处理过程代码如下所示。

        /// <summary>
        /// 自定义异常处理
        /// </summary>
        public class ExceptionHandlingAttribute : ExceptionFilterAttribute
        {
            /// <summary>
            /// 统一对调用异常信息进行处理,返回自定义的异常信息
            /// </summary>
            /// <param name="context">HTTP上下文对象</param>
            public override void OnException(HttpActionExecutedContext context)
            {
                //获取方法或控制器对应的WrapResultAttribute属性
                var actionDescriptor = context.ActionContext.ActionDescriptor;
                var wrapResult = actionDescriptor.GetCustomAttributes<WrapResultAttribute>(inherit: true).FirstOrDefault()
                    ?? actionDescriptor.ControllerDescriptor.GetCustomAttributes<WrapResultAttribute>(inherit: true).FirstOrDefault();
    
                //如设置,记录异常信息
                if (wrapResult != null && wrapResult.LogError)
                {
                    LogHelper.Error(context.Exception);
                }
    
                var statusCode = GetStatusCode(context, wrapResult.WrapOnError);
                if (!wrapResult.WrapOnError)
                {
                    context.Response = new HttpResponseMessage(statusCode) { 
                        Content = new StringContent(context.Exception.Message.ToJson())
                    };
                    context.Exception = null; //Handled!
                    return;
                }
    
                //使用AjaxResponse包装结果
                var content = new ErrorInfo(context.Exception.Message/*, context.Exception.StackTrace*/);
                var isAuth = context.Exception is AuthorizationException;
                context.Response = new HttpResponseMessage(statusCode)
                {
                    Content = new ObjectContent<AjaxResponse>(
                       new AjaxResponse(content, isAuth), JsonFomatterHelper.GetFormatter())
                };
                context.Exception = null; //Handled!
            }

    这样我们在BaseApiController里面声明即可。

      这样,一旦程序处理过程中,有错误抛出,都会统一到这里进行处理,有异常的返回JSON如下所示。

      

    本篇随笔介绍利用AuthorizeAttribute实现Web API身份认证,利用ActionFilterAttribute实现对常规Web API返回结果进行统一格式的封装,利用ExceptionFilterAttribute实现对接口异常的统一处理,实现我们Web API常用到的几个通用处理过程。 

    主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
    专注于Winform开发框架/混合式开发框架Web开发框架Bootstrap开发框架微信门户开发框架的研究及应用
      转载请注明出处:
    撰写人:伍华聪  http://www.iqidi.com 
        
  • 相关阅读:
    在spring中该如何使用DTO,以及DTO和Entity的关系
    AngularJs踩过的坑
    springMVC正确使用GET POST PUT和DELETE方法,如何传递参数
    Mac下使用终端连接远程使用ssh协议的git服务器
    springMVC的异常处理
    根据业务规则分析业务对象,然后生成表结构
    在线资源--图片/json等
    js中的Hook
    diamond源码阅读-目录监控
    maven常用命令
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/14606418.html
Copyright © 2011-2022 走看看