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

  在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"};
}

当然,你也可以根据自己的需要解析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:在不同的情况下会抛出不同的异常,其中一个异常是我们能够预料并认为是调用者传参出错的,一个是不能够处理的,我们看一下结果:

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

关于默认情况下的Filter相关知识我们就讲这么一些,如果在文章中有任何不正确的地方或者疑问,欢迎大家为我指出。

时间: 2024-10-11 07:31:58

WEB API 系列(二) Filter的使用以及执行顺序的相关文章

Web API系列(三)统一异常处理

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 前面讲了webapi的安全验证和参数安全,不清楚的朋友,可以看看前面的文章,<Web API系列(二)接口安全和参数校验>,本文主要介绍Web API异常结果的处理.作为内部或者是对外提供的统一webapi 接口,统一的异常处理,把正确的信息

Web Api系列教程第2季(OData篇)(二)&mdash;&mdash;使用Web Api创建只读的OData服务

前言 很久没更新了,之前有很多事情,所以拖了很久,非常抱歉.好了,废话不多说,下面开始正题.本篇仍然使用上一季的的项目背景(系列地址http://www.cnblogs.com/fzrain/p/3490137.html)来演示OData服务,因此我们可以直接使用之前建好的数据访问层.但是不是说一定要看到之前的所有内容,我们只是借用数据库访问层,对于数据库的模型构建移步(使用Entity Framework Code First构建数据库模型). 有了数据访问的基础,我们可以开始构建OData服

ASP.NET Web API系列教程(目录)(转)

注:微软随ASP.NET MVC 4一起还发布了一个框架,叫做ASP.NET Web API.这是一个用来在.NET平台上建立HTTP服务的Web API框架,是微软的又一项令人振奋的技术.目前,国内对此关注的人似乎还不多,有关ASP.NET Web API的文章也不多见.为此,本人打算对微软ASP.NET Web API官方网站上的一些教程进行翻译,以期让更多的国人了解.学习和使用这项ASP.NET Web API. ASP.NET Web API系列教程目录 Introduction:Wha

ASP.NET Web API系列教程目录

ASP.NET Web API系列教程目录 Introduction:What's This New Web API?引子:新的Web API是什么? Chapter 1: Getting Started with ASP.NET Web API第1章 ASP.NET Web API入门 Your First Web API (C#)第一个Web API(C#) Deep Dive into Web API深入探讨Web API(这是一个视频教程,本翻译系列略) Pro ASP.NET Web

使用 ASP.NET Core MVC 创建 Web API(二)

原文:使用 ASP.NET Core MVC 创建 Web API(二) 使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 六.添加数据库上下文 数据库上下文是使用Entity Framework Core功能的主类. 此类由 Microsoft.EntityFrameworkCore.DbContext 类派生而来. 1) 在Visual Studio 2017的“解决方案资源管理器”中,右键单击“Models”文

[转]Web Api系列教程第2季(OData篇)(二)——使用Web Api创建只读的OData服务

本文转自:http://www.cnblogs.com/fzrain/p/3923727.html 前言 很久没更新了,之前有很多事情,所以拖了很久,非常抱歉.好了,废话不多说,下面开始正题.本篇仍然使用上一季的的项目背景(系列地址http://www.cnblogs.com/fzrain/p/3490137.html)来演示OData服务,因此我们可以直接使用之前建好的数据访问层.但是不是说一定要看到之前的所有内容,我们只是借用数据库访问层,对于数据库的模型构建移步(使用Entity Fram

我这么玩Web Api(二):数据验证,全局数据验证与单元测试

目录 一.模型状态 - ModelState 二.数据注解 - Data Annotations 三.自定义数据注解 四.全局数据验证 五.单元测试   一.模型状态 - ModelState 我理解的ModelState是微软在ASP.NET MVC中提出的一种新机制,它主要实现以下几个功能: 1. 保存客户端传过来的数据,如果验证不通过,把数据返回到客户端,这样可以保存用户输入,不需要重新输入. 2. 验证数据,以及保存数据对应的错误信息. 3. 微软的一种DRY(Don't Repeat

Web Api 系列(一) 初识 web api

HTTP 不仅仅服务于web 页面,而且它也是一个强大的平台 它对于构建提供开放服务和数据的api 来说.HTTP是简单的,灵活的,无处不在的.几乎所有的平台都存在它的身影.HTTP服务可以贯穿于几乎所有的客户端.包括浏览器,移动设备和传统的桌面程序. ASP.NET Web API 是基于.net Framework 用来构建Web 接口的框架.接下来使用asp.net  web api 构建web 接口来返回产品列表. 演示案例需要的软件版本:vs 2013, web api 2. 一 创建

Web API系列(三) 异常处理

在上一篇教程中我为大家介绍了Web API中Filter的开发使用,其中讲到ExceptionFilter时留了一个坑:ExceptionFilter只能截获并处理Action执行过程中发生的异常,在Action执行过程之外如果出现异常,ExceptionFilter是无能为力的. 这些异常包括: 1.  Controller构造方法中出现的异常 2.  MessageHandlers中出现的异常 3.  路由过程中出现的异常 4.  Body在序列化/反序列化过程中出现的异常 由此可以看出,E