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

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

1、Filter的开发和调用数据库

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

Filter 类型api

实现的接口数组

描述服务器

Authorization数据结构

IAuthorizationFilter框架

最早运行的Filter,被用做请求权限校验异步

Actionasync

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处理机制,咱们将在以后的文章中进行讲解。

2、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做用域;

他们遵循了如下规则:

一、在同一做用域下,AuthorizationFilter最早执行,以后执行ActionFilter

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

三、对于ActionFilter,OnActionExecuting老是先于OnActionExecuted执行;

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

五、对于全部Filter来讲,若是阻止了请求:即对Response进行了赋值,则后续的Filter再也不执行。

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

相关文章
相关标签/搜索