转发自:http://www.cnblogs.com/UliiAn/p/5402146.html

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

Filter的开发和调用

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

Filter 类型 实现的接口 描述
Authorization IAuthorizationFilter 最先运行的Filter,被用作请求权限校验
Action IActionFilter 在Action运行的前、后运行
Exception IExceptionFilter 当异常发生的时候运行

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

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

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

下面,我们需要注册这个Filter,注册Filter有三种方法:

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

public class PersonController : ApiController
{
[AuthFilter]
public CreateResult Post(CreateUser user)
{
return new CreateResult() {Id = "123"};
}
}

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

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

[AuthFilter]
public class PersonController : ApiController
{
public CreateResult Post(CreateUser user)
{
return new CreateResult() {Id = "123"};
}
}

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

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

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

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

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

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

我为这个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中也继承了这一方便的特性,使用起来更加方便:

public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}

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

我们可以用下面一个例子来验证:

代码如下:

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

image

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

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

我给大家一个Demo:

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

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

最后一个Filter:ExceptionFilter

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

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

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

[CustomerExceptionFilter]
public class TestController : ApiController
{
public int Get(int a, int b)
{
if (a < b)
{
throw new DemoException("A必须要比B大!");
}
if (a == b)
{
throw new NotImplementedException();
}
return a*b;
}
}

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

image

image

在这样的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不再执行。

Asp.Net WebAPI中Filter过滤器的使用以及执行顺序的更多相关文章

  1. ASP.NET WebAPI 13 Filter

    Filter(筛选器)是基于AOP(面向方面编程)的设计,它的作用是Actionr的执行注入额外的逻辑,以达到横切注入的目的. IFilter 在WebAPI中所以的Filter都实现了IFilter ...

  2. 关于ASP.NET WebAPI中HTTP模型的相关思考

    对于.NET的分布式应用开发,可以供我们选择的技术和框架比较多,例如webservice,.net remoting,MSMQ,WCF等等技术.对于这些技术很多人都不会陌生,即时没有深入的了解,但是肯 ...

  3. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  4. [Vue]vue中各选项及钩子函数执行顺序

    在vue中,实例选项和钩子函数和{{}}表达式都是不需要手动调用就可以直接执行的. 一.生命周期图示 二.vue中各选项及钩子函数执行顺序 1.在页面首次加载执行顺序有如下: beforeCreate ...

  5. python 中多个装饰器的执行顺序

    python 中多个装饰器的执行顺序: def wrapper1(f1): print('in wrapper1') def inner1(*args,**kwargs): print('in inn ...

  6. java 中 this 和 super 说明及在构造器中super()和this()相互调用执行顺序

    this this 表示当前对象 使用上细分的话,this有 this. 和this()的使用情况 ,下面我们开始细撸 this . 使用场景一: 在成员方法中,this.变量名 指带当前对象的变量, ...

  7. 在asp.net WebAPI 中 使用Forms认证和ModelValidata(模型验证)

    一.Forms认证 1.在webapi项目中启用Forms认证 Why:为什么要在WebAPI中使用Forms认证?因为其它项目使用的是Forms认证. What:什么是Forms认证?它在WebAP ...

  8. [翻译] ASP.NET WebAPI 中的异常处理

    原文链接:https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling 本文介绍 ...

  9. 在ASP.NET WebAPI 中使用缓存【Redis】

    初步看了下CacheCow与OutputCache,感觉还是CacheOutput比较符合自己的要求,使用也很简单 PM>Install-Package Strathweb.CacheOutpu ...

随机推荐

  1. 二维码js生成库

    jr-qrcode 把字符串生成二维码,并以Base64 URL形式输出. 支持白色二维码,即反色二维码. 兼容性 插件使用了H5的canvas特性进行二维码绘制,最后输出base64 url,因此本 ...

  2. 网站SEO优化问答精选

    1.百度每更新一次,网站的收录就减少很多,但是我每天都增加伪原创的内容啊? 这个问题大多数是因为网站权重导致百度不够重视你:另外就是文章质量度不高,没有可读性或是原创度太低,尽管百度会收录,但是经过一 ...

  3. JDBC基本使用

    J2EE技术规范(二)——JDBC 分类: java2012-12-03 14:25 1060人阅读 评论(8) 收藏 举报 一.了解JDBC (1) JDBC是以统一方式访问数据库的API (2) ...

  4. Spring Cloud入门教程 - Zuul实现API网关和请求过滤

    简介 Zuul是Spring Cloud提供的api网关和过滤组件,它提供如下功能: 认证 过滤 压力测试 Canary测试 动态路由 服务迁移 负载均衡 安全 静态请求处理 动态流量管理 在本教程中 ...

  5. (原创)ubuntu 10.04+ruby1.9.2+rails3 安装记录

    第一步当然是现在ruby 1.9.2 的sourcecode了,因为现在的ubuntu 源中还没有1.9.2的版本 我下载的是ruby-1.9.2-p290.tar.gz 然后解压到/usr/loca ...

  6. React+ANTD项目使用后的一些关于生命周期比较实用的心得

    1. constructor() constructor(props){ super(props) this.state=({ }) } 一定先写super  可以接收从父组件传来的值 父组件往子组件 ...

  7. java原子操作的实现原理--转载

    原文地址:http://www.infoq.com/cn/articles/atomic-operation 1. 引言 原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic ...

  8. cocos2d-x 欢乐捕鱼游戏总结

    这几天一直都在做一个捕鱼游戏Demo,大概花掉了我快一个礼拜的时间.游戏主体是使用的cocos2d-x高级开发教程里面提供的小部分框架基本功能.然后自己加入所有的UI元素和玩法.变成了一个体验不错的捕 ...

  9. Flask入门之触发器,事件,数据迁移

    SQLAlchemy Core和SQLAlchemy ORM都具有各种各样的事件挂钩: 核心事件 - 这些在 Core Events中描述,并包括特定于连接池生命周期,SQL语句执行,事务生命周期以及 ...

  10. 基于ubuntu16.04快速构建Hyperledger Fabric网络

    前言 最近在参加一个比赛,使用到了区块链的开源软件hyperledger,由于之前从未接触过区块链,以及和区块链开发相关的内容,所有在网上查阅了大量的资料,并且通过学习yeasy(杨宝华)开源的入门书 ...