在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再也不執行