利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理

简介: 利用过滤器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_Start\WebApiConfig.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常用到的几个通用处理过程。

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com

相关文章
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
60 4
|
2月前
|
SQL 安全 PHP
PHP 自发布以来一直在 Web 开发领域占据重要地位,PHP 8 更是带来了属性、刚性类型等新特性。
【10月更文挑战第1天】PHP 自问世以来,凭借其易用性和灵活性,在 Web 开发领域迅速崛起。从简单的网页脚本语言逐步演进为支持面向对象编程的现代语言,尤其自 PHP 5.3 引入命名空间后,代码组织和维护变得更加高效。PHP 7 的性能优化和 PHP 8 的新特性(如属性和刚性类型)进一步巩固了其地位。框架如 Laravel、Symfony、Yii2 和 CodeIgniter 等简化了开发流程,提高了效率和安全性。
54 2
|
3月前
|
缓存 测试技术 API
API的封装步骤流程
API封装流程是一个系统化的过程,旨在将内部功能转化为可复用的接口供外部调用。流程包括明确需求、设计接口、选择技术和工具、编写代码、测试、文档编写及部署维护。具体步骤为确定业务功能、数据来源;设计URL、请求方式、参数及响应格式;选择开发语言、框架和数据库技术;实现数据连接、业务逻辑、错误处理;进行功能、性能测试;编写详细文档;部署并持续维护。通过这些步骤,确保API稳定可靠,提高性能。
|
4月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
131 0
|
25天前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
41 6
|
3月前
|
前端开发
【前端web入门第四天】02 CSS三大特性+背景图
本文详细介绍了CSS的三大特性:继承性、层叠性和优先级,并深入讲解了背景图的相关属性,包括背景属性、背景图的平铺方式、位置设定、缩放、固定以及复合属性。其中,继承性指子元素自动继承父元素的文字控制属性;层叠性指相同属性后定义覆盖前定义,不同属性可叠加;优先级涉及选择器权重,包括行内样式、ID选择器等。背景图部分则通过具体示例展示了如何设置背景图像的位置、大小及固定方式等。
267 91
|
2月前
|
缓存 JavaScript 前端开发
深入理解 Vue 3 的 Composition API 与新特性
本文详细探讨了 Vue 3 中的 Composition API,包括 setup 函数的使用、响应式数据管理(ref、reactive、toRefs 和 toRef)、侦听器(watch 和 watchEffect)以及计算属性(computed)。我们还介绍了自定义 Hooks 的创建与使用,分析了 Vue 2 与 Vue 3 在响应式系统上的重要区别,并概述了组件生命周期钩子、Fragments、Teleport 和 Suspense 等新特性。通过这些内容,读者将能更深入地理解 Vue 3 的设计理念及其在构建现代前端应用中的优势。
48 1
深入理解 Vue 3 的 Composition API 与新特性
|
1月前
|
JavaScript 前端开发 API
Vue 3新特性详解:Composition API的威力
【10月更文挑战第25天】Vue 3 引入的 Composition API 是一组用于组织和复用组件逻辑的新 API。相比 Options API,它提供了更灵活的结构,便于逻辑复用和代码组织,特别适合复杂组件。本文将探讨 Composition API 的优势,并通过示例代码展示其基本用法,帮助开发者更好地理解和应用这一强大工具。
36 2
|
3月前
|
存储 JavaScript 前端开发
敲黑板!vue3重点!一文了解Composition API新特性:ref、toRef、toRefs
该文章深入探讨了Vue3中Composition API的关键特性,包括`ref`、`toRef`、`toRefs`的使用方法与场景,以及它们如何帮助开发者更好地管理组件状态和促进逻辑复用。
敲黑板!vue3重点!一文了解Composition API新特性:ref、toRef、toRefs
|
3月前
|
API PHP
ThinkPHP 通用的API格式封装
本文介绍了在ThinkPHP框架中如何统一封装API返回格式的方法,包括创建状态码枚举类、编写统一格式化函数以及在BaseController和Error控制器中重写`__call`方法来处理不存在的方法或控制器调用,以实现统一的错误处理和返回格式。
ThinkPHP 通用的API格式封装