在WEB Api中,引入了面向切面程式設計(AOP)的思想,在某些特定的位置可以插入特定的Filter進行製程攔截處理。引入了這個機制可以更好地實踐DRY(Don't Repeat Yourself)思想,透過Filter能統一地對一些通用邏輯進行處理,如:權限校驗、參數加解密、參數校驗等方面我們都可以利用這項特性進行統一處理,今天我們來介紹Filter的開發、使用以及討論他們的執行順序。
一、Filter的開發和呼叫
在預設的WebApi中,框架提供了三種Filter,他們的功能和運作條件如下表所示:
#Filter |
##實作的介面 |
描述 |
Authorization |
IAuthorizationFilter |
最先運行的Filter ,被用作請求權限校驗 |
Action |
IActionFilter |
在Action 運行的前、後運行 |
#Exception |
IExceptionFilter |
當異常發生的時候執行 |
首先,我們實作一個AuthorizatoinFilter可以用以簡單的權限控制:
(actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute> verifyResult = actionContext.Request.Headers.Authorization!= && == ; (!= HttpError(
一個簡單的用於用戶驗證的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實例:
{ id =
用這個方式適合於控制所有的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相关知识我们就讲这么一些,如果在文章中有任何不正确的地方或者疑问,欢迎大家为我指出。
以上是Asp.Net WebAPI中 Filter的使用以及執行順序(收藏)的詳細內容。更多資訊請關注PHP中文網其他相關文章!