在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理。引入了这一机制可以更好地践行DRY(Don’t Repeat Yourself)思想,通过Filter能统一地对一些通用逻辑进行处理,如:权限校验、参数加解密、参数校验等方面我们都可以利用这一特性进行统一处理,今天我们来介绍Filter的开发、使用以及讨论他们的执行顺序。

一、Filter的开发和调用

在默认的WebApi中,框架提供了三种Filter,他们的功能和运行条件如下表所示:

Filter 类型

实现的接口

描述

Authorization

IAuthorizationFilter

最先运行的Filter,被用作请求权限校验

Action

IActionFilter

在Action运行的前、后运行

Exception

IExceptionFilter

当异常发生的时候运行

首先,我们实现一个AuthorizatoinFilter可以用以简单的权限控制:

  1. public class AuthFilterAttribute : AuthorizationFilterAttribute
  2. {
  3. public override void OnAuthorization(HttpActionContext actionContext)
  4. {
  5. //如果用户方位的Action带有AllowAnonymousAttribute,则不进行授权验证
  6. if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
  7. {
  8. return;
  9. }
  10. var verifyResult = actionContext.Request.Headers.Authorization!=null && //要求请求中需要带有Authorization头
  11. actionContext.Request.Headers.Authorization.Parameter == "123456"; //并且Authorization参数为123456则验证通过
  12.  
  13. if (!verifyResult)
  14. {
  15. //如果验证不通过,则返回401错误,并且Body中写入错误原因
  16. actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized,new HttpError("Token 不正确"));
  17. }
  18. }
    }

一个简单的用于用户验证的Filter就开发完了,这个Filter要求用户的请求中带有Authorization头并且参数为123456,如果通过则放行,不通过则返回401错误,并在Content中提示Token不正确。下面,我们需要注册这个Filter,注册Filter有三种方法:

第一种:在我们希望进行权限控制的Action上打上AuthFilterAttribute这个Attribute:

  1. public class PersonController : ApiController
  2. {
  3. [AuthFilter]
  4. public CreateResult Post(CreateUser user)
  5. {
  6. return new CreateResult() {Id = "123"};
  7. }
  8. }

这种方式适合单个Action的权限控制。

第二种,找到相应的Controller,并打上这个Attribute:

  1. [AuthFilter]
  2. public class PersonController : ApiController
  3. {
  4. public CreateResult Post(CreateUser user)
  5. {
  6. return new CreateResult() {Id = "123"};
  7. }
  8. }

这种方式适合于控制整个Controller,打上这个Attribute以后,整个Controller里所有Action都获得了权限控制。

第三种,找到App_Start\WebApiConfig.cs,在Register方法下加入Filter实例:

  1. public static void Register(HttpConfiguration config)
  2. {
    config.MapHttpAttributeRoutes();
       //注册全局Filter
  3. config.Filters.Add(new AuthFilterAttribute());
  4. config.Routes.MapHttpRoute(
  5. name: "DefaultApi",
  6. routeTemplate: "api/{controller}/{id}",
  7. defaults: new { id = RouteParameter.Optional }
  8. );
  9. }

用这种方式适合于控制所有的API,任意Controller和任意Action都接受了这个权限控制。

在大多数场景中,每个API的权限验证逻辑都是一样的,在这样的前提下使用全局注册Filter的方法最为简单便捷,可这样存在一个显而易见的问题:如果某几个API是不需要控制的(例如登录)怎么办?我们可以在这样的API上做这样的处理:

  1. [AllowAnonymous]
  2. public CreateResult PostLogin(LoginEntity entity)
  3. {
  4. //TODO:添加验证逻辑
  5. return new CreateResult() {Id = "123456"};
  6. }

我为这个Action打上了AllowAnonymousAttribute,验证逻辑就放过了这个API而不进行权限校验。

在实际的开发中,我们可以设计一套类似Session的机制,通过用户登录来获取Token,在之后的交互HTTP请求中加上Authorization头并带上这个Token,并在自定义的AuthFilterAttribute中对Token进行验证,一套标准的Token验证流程就可以实现了。

接下来我们介绍ActionFilter:

  ActionFilterAttrubute提供了两个方法进行拦截:OnActionExecuting和OnActionExecuted,他们都提供了同步和异步的方法。

  OnActionExecuting方法在Action执行之前执行,OnActionExecuted方法在Action执行完成之后执行。

  我们来看一个应用场景:使用过MVC的同学一定不陌生MVC的模型绑定和模型校验,使用起来非常方便,定义好Entity之后,在需要进行校验的地方可以打上相应的Attribute,在Action开始时检查ModelState的IsValid属性,如果校验不通过直接返回View,前端可以解析并显示未通过校验的原因。而Web API中也继承了这一方便的特性,使用起来更加方便:

  1. public class CustomActionFilterAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuting(HttpActionContext actionContext)
  4. {
  5. if (!actionContext.ModelState.IsValid)
  6. {
  7. actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
  8. }
  9. }
  10. }

这个Filter就提供了模型校验的功能,如果未通过模型校验则返回400错误,并把相关的错误信息交给调用者。他的使用方法和AuthFilterAttribute一样,可以针对Action、Controller、全局使用。我们可以用下面一个例子来验证:

代码如下:

  1. public class LoginEntity
  2. {
  3. [Required(ErrorMessage = "缺少用户名")]
  4. public string UserName { get; set; }
  5.  
  6. [Required(ErrorMessage = "缺少密码")]
  7. public string Password { get; set; }
  8. }
  1. [AllowAnonymous]
  2. [CustomActionFilter]
  3. public CreateResult PostLogin(LoginEntity entity)
  4. {
  5. //TODO:添加验证逻辑
  6. return new CreateResult() {Id = "123456"};
  7. }

当然,你也可以根据自己的需要解析ModelState然后用自己的格式将错误信息通过Request.CreateResponse()返回给用户。

  OnActionExecuted方法我在实际工作中使用得较少,目前仅在一次部分响应数据加密的场景下进行过使用,使用方法一样,读取已有的响应,并加密后再给出加密后的响应赋值给actionContext.Response即可。

我给大家一个Demo:

  1. public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
  2. {
  3. var key = 10;
  4.  
  5. var responseBody = await actionExecutedContext.Response.Content.ReadAsByteArrayAsync(); //以Byte数组方式读取Content中的数据
  6.  
  7. for (int i = 0; i < responseBody.Length; i++)
  8. {
  9. responseBody[i] = (byte)(responseBody[i] ^ key); //对每一个Byte做异或运算
  10. }
  11.  
  12. actionExecutedContext.Response.Content = new ByteArrayContent(responseBody); //将结果赋值给Response的Content
  13.  
  14. actionExecutedContext.Response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("Encrypt/Bytes"); //并修改Content-Type
  15. }

  通过这个方法我们将响应的Content每个Byte都做了一个异或运算,对响应内容进行了一次简单的加密,大家可以根据自己的需要进行更可靠的加密,如AES、DES或者RSA…通过这个方法可以灵活地对某个Action的处理后的结果进行处理,通过Filter进行响应内容加密有很强的灵活性和通用性,他能获取当前Action的很多信息,然后根据这些信息选择加密的方式、获取加密所需的参数等等。如果加密所使用参数对当前执行的Action没有依赖,也可以采取HttpMessageHandler来进行处理,在之后的教程中我会进行介绍。

最后一个Filter:ExceptionFilter

顾名思义,这个Filter是用来进行异常处理的,当业务发生未处理的异常,我们是不希望用户接收到黄页或者其他用户无法解析的信息的,我们可以使用ExceptionFilter来进行统一处理:

  1. public class ExceptionFilter : ExceptionFilterAttribute
  2. {
  3. public override void OnException(HttpActionExecutedContext actionExecutedContext)
  4. {
  5. //如果截获异常为我们自定义,可以处理的异常则通过我们自己的规则处理
  6. if (actionExecutedContext.Exception is DemoException)
  7. {
  8. //TODO:记录日志
  9. actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(
  10. HttpStatusCode.BadRequest, new {Message = actionExecutedContext.Exception.Message});
  11. }
  12. else
  13. {
  14. //如果截获异常是我没无法预料的异常,则将通用的返回信息返回给用户,避免泄露过多信息,也便于用户处理
  15.  
  16. //TODO:记录日志
  17. actionExecutedContext.Response =
  18. actionExecutedContext.Request.CreateResponse(HttpStatusCode.InternalServerError,
  19. new {Message = "服务器被外星人拐跑了!"});
  20. }
  21. }
  22. }

我们定义了一个ExceptoinFilter用于处理未捕获的异常,我们将异常分为两类:一类是我们可以预料的异常:如业务参数错误,越权等业务异常;还有一类是我们无法预料的异常:如数据库连接断开、内存溢出等异常。我们通过HTTP Code告知调用者以及用相对固定、友好的数据结构将异常信息告诉调用者,以便于调用者记录并处理这样的异常。

  1. [CustomerExceptionFilter]
  2. public class TestController : ApiController
  3. {
  4. public int Get(int a, int b)
  5. {
  6. if (a < b)
  7. {
  8. throw new DemoException("A必须要比B大!");
  9. }
  10. if (a == b)
  11. {
  12. throw new NotImplementedException();
  13. }
  14. return a*b;
  15. }
  16. }

我们定义了一个Action:在不同的情况下会抛出不同的异常,其中一个异常是我们能够预料并认为是调用者传参出错的,一个是不能够处理的,我们看一下结果:

在这样的RestApi中,我们可以预先定义好异常的表现形式,让调用者可以方便地判断什么情况下是出现异常了,然后通过较为统一的异常信息返回方式让调用者方便地解析异常信息,形成统一方便的异常消息处理机制。

    但是,ExceptionFilter只能在成功完成了Controller的初始化以后才能起到捕获、处理异常的作用,而在Controller初始化完成之前(例如在Controller的构造函数中出现了异常)则ExceptionFilter无能为力。对此WebApi引入了ExceptionLogger和ExceptionHandler处理机制,我们将在之后的文章中进行讲解。

二、Filter的执行顺序

在使用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作用域;

他们遵循了以下规则:

1、在同一作用域下,AuthorizationFilter最先执行,之后执行ActionFilter

2、对于AuthorizationFilter和ActionFilter.OnActionExcuting来说,如果一个请求的生命周期中有多个Filter的话,执行顺序都是Global->Controller->Action;

3、对于ActionFilter,OnActionExecuting总是先于OnActionExecuted执行;

4、对于ExceptionFilter和ActionFilter.OnActionExcuted而言执行顺序为Action->Controller->Global;

5、对于所有Filter来说,如果阻止了请求:即对Response进行了赋值,则后续的Filter不再执行

WEB API Filter的使用以及执行顺序的更多相关文章

  1. WEB API 系列(二) Filter的使用以及执行顺序

    在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理.引入了这一机制可以更好地践行DRY(Don’t Repeat Yourself)思想 ...

  2. Web API系列(二) Filter的使用以及执行顺序

    在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理.引入了这一机制可以更好地践行DRY(Don’t Repeat Yourself)思想 ...

  3. web.xml文件中context-param、listener、filter、servlet的执行顺序

    首先可以肯定的是,加载顺序与它们在 web.xml 文件中的先后顺序无关.即不会因为 filter 写在 listener 的前面而会先加载 filter.最终得出的结论是:listener -> ...

  4. Web API Filter

    在Web Api中,有三种Filter Filter类型 实现的接口 描述 Authorization IAuthorizationFilter 最先运行的Filter,被用作请求权限校验 Actio ...

  5. Web API Filter ActionFilterAttribute 使用

    WebApi 提供两种过滤器的类型: 1.ActionFilterAttribute 2.exceptionFilterAttribute 两个类都是抽象类,ActionFilter 主要实现执行请求 ...

  6. web.xml中多个Servlet执行顺序的问题!

    1.两个servlet或者两个servlet-mapping,其中的servlet-name名称不能存在相同. 2.所有的servlet-mapping标签下,url-pattern中包含的文本不能相 ...

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

    在我们开发Web API应用的时候,我们可以借鉴ABP框架的过滤器Filter和特性Attribute的应用,实现对Web API返回结果的封装和统一异常处理,本篇随笔介绍利用AuthorizeAtt ...

  8. MVC和Web API 过滤器Filter

    MVC和Web API Filter(过滤器) ASP.NET MVC 支持以下类型的操作筛选器: ·        授权筛选器.这些筛选器用于实现IAuthorizationFilter和做出关于是 ...

  9. Asp.net Mvc (Filter及其执行顺序)

    应用于Action的Filter 在Asp.netMvc中当你有以下及类似以下需求时你可以使用Filter功能判断登录与否或用户权限,决策输出缓存,防盗链,防蜘蛛,本地化设置,实现动态Actionfi ...

随机推荐

  1. Touch事件详解及区别,触屏滑动距离计算

    移动端有四个关于触摸的事件,分别是touchstart.touchmove.touchend.touchcancel(比较少用), 它们的触发顺序是touchstart-->touchmove- ...

  2. sql关于对一个字段同时满足多条件判断来筛选查询

    表所有数据 查询userName为abc或xyz的 以下为本菜鸟项目中遇到的问题: 背景: /**  * wangjie 180629  *   * 学生需要查询四种可能的消息  * 1.班级管理员发 ...

  3. Codeforces.578E.Walking(构造)

    题目链接 \(Description\) 给定一个长为\(n\)的足迹序列(只包含\(L,R\)两种字符),你需要\(LRLRLR...\)这样交替在\(L\)和\(R\)上走(第一步可以选择\(L\ ...

  4. [POJ1006]生理周期 (中国剩余定理)

    蒟蒻并不会中国剩余定理 交的时候还出现了PE的错误 下面是AC代码 #include<iostream> #include<cstdio> using namespace st ...

  5. Android 蓝牙4.0 BLE (onServicesDiscovered 返回 status 是 129,133时)

    Android ble (Bluetooth Low Energy) 蓝牙4.0,也就是说android 4.3+, API level >= 18,且支持蓝牙4.0的手机才可以使用. BLE是 ...

  6. Little Pony and Alohomora Part 3 [HihoCoder 1075]

    描述 一日,崔克茜来到小马镇表演魔法. 其中有一个节目是开锁咒:舞台上有 n 个盒子,每个盒子中有一把钥匙,对于每个盒子而言有且仅有一把钥匙能打开它.初始时,崔克茜将会随机地选择 k 个盒子用魔法将它 ...

  7. Linux安装gcc时碰到的有关问题解决(解决gcc依赖有关问题)

    Linux安装gcc时碰到的有关问题解决(解决gcc依赖有关问题) rpm安装gcc时碰到的有关问题解决(解决gcc依赖有关问题) 提示:error: Failed dependencies: clo ...

  8. oracle增加表空间大小

    第一步:查看表空间的名字及文件所在位置: select tablespace_name, file_id, file_name, round(bytes/(1024*1024),0) total_sp ...

  9. 小甲鱼python第二讲课后习题

    0.什么是BIF BIF为内置函数,英语全称为Build-in-Function Python3用input()取代了Python2的raw_input(),接收用户输入 1.用课堂上小甲鱼教的方法数 ...

  10. JSP(6)—JavaBean及案例

    基础: 一.JavaBean ①用作JavaBean的类必须是具有一个公共的无参数的构造方法 ②JavaBean的属性是以方法定义的形式出现的. ③JavaBean的属性名是根据Setter和gett ...