在我们开发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常用到的几个通用处理过程。

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

  1. 利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理

    在Asp.net Web API中,对业务数据的分页查询处理是一个非常常见的接口,我们需要在查询条件对象中,定义好相应业务的查询参数,排序信息,请求记录数和每页大小信息等内容,根据这些查询信息,我们在 ...

  2. c#调用js,以及js调用C#里的函数, c#自己生成js代码,实现对web的控制

    using mshtml;using System;using System.Collections.Generic;using System.Linq;using System.Security.P ...

  3. 通过脚本实现对web的健康检查

    前面的文章中(https://www.cnblogs.com/zyxnhr/p/10707932.html),通过nginx的第三方模块实现对web端的一个监控,现在通过一个脚本实现对第三方的监控 脚 ...

  4. 返璞归真 asp.net mvc (11) - asp.net mvc 4.0 新特性之自宿主 Web API, 在 WebForm 中提供 Web API, 通过 Web API 上传文件, .net 4.5 带来的更方便的异步操作

    原文:返璞归真 asp.net mvc (11) - asp.net mvc 4.0 新特性之自宿主 Web API, 在 WebForm 中提供 Web API, 通过 Web API 上传文件, ...

  5. Filter execute order in asp.net web api

    https://stackoverflow.com/questions/21628467/order-of-execution-with-multiple-filters-in-web-api Som ...

  6. SpringBoot 利用过滤器Filter修改请求url地址

    要求: 代码中配置的url路径为http://127.0.0.1/api/associates/queryAssociatesInfo 现在要求http://127.0.0.1/associates/ ...

  7. 利用Python实现对Web服务器的目录探测

    今天是一篇提升技能的干货分享,操作性较强,适用于中级水平的小伙伴,文章阅读用时约3分钟. PART 1/Python Python是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python ...

  8. 利用 python 实现对web服务器的目录探测

    一.pythonPython是一种解释型.面向对象.动态数据类型的高级程序设计语言.python 是一门简单易学的语言,并且功能强大也很灵活,在渗透测试中的应用广泛,让我们一起打造属于自己的渗透测试工 ...

  9. 利用Array Prototype的方法来实现对dom集合的筛选、indexOf、map等功能

    <!DOCTYPE html><html> <head> <title>TODO supply a title</title> <me ...

随机推荐

  1. AirPods 2 声音非常小

    AirPods 2 声音非常小 bug 停用蓝牙绝对音量 可能是因为开启了耳机的绝对音量功能; 绝对音量功能开启后,耳机的音量和手机音量分开, 可以在手机设置里停用绝对音量; refs http:// ...

  2. vuex bug & vue computed setter

    vuex bug & vue computed setter https://vuejs.org/v2/guide/computed.html#Computed-Setter [Vue war ...

  3. nginx判断状态脚本

    A是nginx行数 为0则启动nginx 启动失败则杀死keepalived进程

  4. SpringCloud Stream整合RabbitMQ3.5.0

    前言 点击进入Spring官网文档 本文章为单体项目,将消费者和生产者写在同一个项目中,介意者不用向下看了. 本文介绍三种应用方式: 1:普通整合RabbitMQ 2:消息分区 3:按条件消费(多个消 ...

  5. Spring-03 依赖注入(DI)

    Spring-03 依赖注入(DI) 依赖注入(DI) 依赖注入(Dependency Injection,DI). 依赖 : 指Bean对象的创建依赖于容器,Bean对象的依赖资源. 注入 : 指B ...

  6. ADT基础(二)—— Tree,Heap and Graph

    ADT基础(二)-- Tree,Heap and Graph 1 Tree(二叉树) 先根遍历 (若二叉树为空,则退出,否则进行下面操作) 访问根节点 先根遍历左子树 先根遍历右子树 退出 访问顺序为 ...

  7. MySQL确认注入点

    目录 WHERE子句后面的注入点 逻辑符号AND.OR other order by union limit table WEB渗透测试流程中,初期工作是进行信息收集,完成信息收集之后,就会进行漏洞测 ...

  8. go 报错 import cycle not allowed

    运行时报错,import cycle not allowed : 查了goole大概知道了原因,还是导包类的问题,我检察了一下我的代码库,发现我昨天划分几个工具文件,里面的两个文件相互引用,就导致报i ...

  9. 【DP】斜率优化初步

    向y总学习了斜率优化,写下这篇blog加深一下理解. 模板题:https://www.acwing.com/problem/content/303/ 分析 因为本篇的重点在于斜率优化,故在此给出状态转 ...

  10. React函数式组件和类组件[Dan]

    一篇对Dan的 How Are Function Components Different from Classes? 一文的个人阅读总结,内容来自于此.强烈推荐阅读 Dan Abramov.的博客. ...