在WEB Api中,引入了面向切面編程(AOP)的思想,在某些特定的位置可以插入特定的Filter進行過程攔截處理。引入了這一機制可以更好地踐行DRY(Don’t Repeat Yourself)思想,通過Filter能統一地對一些通用邏輯進行處理,如:權限校驗、參數加解密、參數校驗等方面我們都可以利用這一特性進行統一處理,今天我們來介紹Filter的開發、使用以及討論他們的執行順序。
一、Filter的使用
在默認的WebApi中,框架提供了三種Filter,他們的功能和運行條件如下表所示:
Filter 類型 |
實現的接口 |
描述 |
Authorization |
IAuthorizationFilter |
最先運行的Filter,被用作請求權限校驗 |
Action |
IActionFilter |
在Action運行的前、后運行 |
Exception |
IExceptionFilter |
當異常發生的時候運行 |
1.授權攔截過濾器
首先,我們實現一個CustomAuthorizeAttribute可以用以簡單的權限控制,(此處AuthorizeAttribute繼承自AuthorizationFilterAttribute)用於接口授權:
/// <summary> /// 自定義授權 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class CustomAuthorizeAttribute:AuthorizeAttribute { /// <summary> /// 用戶授權 /// </summary> /// <param name="actionContext"></param> /// <returns></returns> public override void OnAuthorization(HttpActionContext actionContext) { //url獲取token var content = actionContext.Request.Properties["MS_HttpContext"] as HttpContextBase; HttpRequestBase request = content.Request; string access_key = request.Params["access_key"];//不管是post請求還是get請求,都從地址欄獲取key跟sign string sign = request.Params["sign"]; //校驗IP var data=XmlUtil.IsAuthorization(HttpContext.Current.Request.UserHostAddress); if (data.IsExistence)//ip是否在白名單內 { if ("1".Equals(data.IsAuthorization))//是否需要參與簽名驗證 { if (!string.IsNullOrEmpty(access_key) && !string.IsNullOrEmpty(sign)) { var key = "7058e63cdf0646948201";//隨便固定一個key,真實使用的化可以從數據庫獲取或配置文件讀取 var s = MD5Encrypt32(string.Format("{0}{1}", access_key, key)); if ("2c3a368b-26a8-4a4d-a204-14bd6388f3c2".Equals(access_key) && s.Equals(sign))//驗證通過放行 { base.IsAuthorized(actionContext); } else { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new { code = HttpStatusCode.Unauthorized, data = "", message = "access_key 或 sign 參數有誤,請核對", }); } } else { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new { code = HttpStatusCode.Unauthorized, data = "", message = "access_key 或 sign 校驗參數未從地址欄帶入,請核對", }); } } else { base.IsAuthorized(actionContext); } } else { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new { code = HttpStatusCode.Unauthorized, data = "", message = $"IP:{HttpContext.Current.Request.UserHostAddress}沒有調用服務的權限", }); } } /// <summary> /// 32位MD5加密 /// </summary> /// <param name="password"></param> /// <returns></returns> public string MD5Encrypt32(string password) { string pwd = ""; MD5 md5 = MD5.Create(); //實例化一個md5對像 // 加密后是一個字節類型的數組,這里要注意編碼UTF8/Unicode等的選擇 byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password)); // 通過使用循環,將字節類型的數組轉換為字符串,此字符串是常規字符格式化所得 for (int i = 0; i < s.Length; i++) { // 將得到的字符串使用十六進制類型格式。格式后的字符是小寫的字母,如果使用大寫(X)則格式后的字符是大寫字符 pwd = pwd + s[i].ToString("X"); } return pwd.ToLower(); } }
2.錯誤異常捕獲過濾器
當服務端代碼報錯或出異常時,可自定義設置固定格式的異常返回給調用者,具體實現如下:
/// <summary> /// 自定義錯誤異常捕獲 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class CustomExceptionAttribute:ExceptionFilterAttribute { /// <summary> /// 異常發生 /// </summary> /// <param name="actionExecutedContext"></param> public override void OnException(HttpActionExecutedContext actionExecutedContext) { //記錄錯誤日志 Task.Run(() => { //此處可以調用記錄日志方法 }); actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(HttpStatusCode.InternalServerError, new { code = HttpStatusCode.InternalServerError, message = actionExecutedContext.Exception.Message }); } }
3.請求成功過濾器
如果要把所有接口返回類型參數固定可以使用ActionFilter過濾器,簡單實現如下:
/// <summary> /// 自定義統一信息格式返回 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class CustomActionAttribute:ActionFilterAttribute { /// <summary> /// 請求成功之后 /// </summary> /// <param name="actionExecutedContext"></param> public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(actionExecutedContext.Response.StatusCode, new { code = 200, data =JsonConvert.DeserializeObject(actionExecutedContext.Response.Content.ReadAsStringAsync().Result),//返回給調用者的數據 message ="success" }); } }
接口調用成功之后返回給調用者的數據格式都是固定格式,調用者使用起來就很方便,返回固定格式如下:
{ code = 200, data ="數據" message ="success" }
二、過濾器的使用及注冊
過濾器注冊方式有三種:
- 全局注冊
- 類注冊
- 方法注冊
1.全局注冊
全局注冊時在App_Start==>WebApiConfig.cs文件里面,注冊代碼如下:
public static class WebApiConfig { /// <summary> /// /// </summary> /// <param name="config"></param> public static void Register(HttpConfiguration config) { // 身份認證篩選器。 config.Filters.Add(new CustomAuthorizeAttribute());//這是全局注冊 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.EnsureInitialized(); } }
2.控制器注冊
控制器注冊就是在Controller類里面進行注冊,注冊代碼如下:
/// <summary> /// /// </summary> [CustomAuthorizeAttribute] //控制器注冊 public class DepartmentApiController : ApiController { }
3.方法注冊
方法注冊就是在Controller里面使用的方法上進行標記注冊,注冊代碼如下:
/// <summary> /// /// </summary> public class DepartmentApiController : ApiController { [CustomAuthorizeAttribute]//方法注冊 [HttpGet] public object GetDepartmentList() { }
}
三、過濾器執行優先級
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,AllowMultiple = false)]//AllowMultiple=false時 執行優先級如下
同一個過濾器分別用在了全局、控制器和行為方法中,執行同一個方法時都會有先后順序,如果按默認值(不設Order的情況下),一般的順序是由最外層到最里層,就是“全局”——>“控制器”——>“行為方法”;
而特別的就是錯誤處理的過濾器,由於異常是由里往外拋的,所以它的順序剛好也反過來:“行為方法”——>“控制器”——>“全局”。
以上是開發webapi過濾器的簡單用法,以上代碼還需要優化,有需要的朋友自己封裝優化一下就可以使用了。