在我們開發Web API應用的時候,我們可以借鑒ABP框架的過濾器Filter和特性Attribute的應用,實作對Web API回傳結果的封裝和統一例外處理,本篇隨筆介紹利用AuthorizeAttribute實作Web API身份認證,利用ActionFilterAttribute實作對常規Web API回傳結果進行統一格式的封裝,利用ExceptionFilterAttribute實作對介面例外的統一處理,實作我們Web API常用到的幾個通用處理程序,
1、Asp.net的Web API過濾器介紹
過濾器主要有這么幾種:AuthorizationFilterAttribute 權限驗證、ActionFilterAttribute 日志引數驗證等、ExceptionFilterAttribute 例外處理捕獲,
ActionFilter 主要實作執行請求方法體之前(覆寫基類方法OnActionExecuting),和之后的事件處理(覆寫基類方法OnActionExecuted);ExceptionFilter主要實作觸發例外方法(覆寫基類方法OnException),
ActionFilterAttrubute提供了兩個方法進行攔截:
- OnActionExecuting和OnActionExecuted,他們都提供了同步和異步的方法,
- OnActionExecuting方法在Action執行之前執行,OnActionExecuted方法在Action執行完成之后執行,
在使用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不再執行,
另外,值得注意的是,由于Web API的過濾器無法改變其順序,那么它是按照 AuthorizationFilterAttribute -> ActionFilterAttribute -> ExceptionFilterAttribute 這個執行順序來處理的,也就是說授權過濾器執行在前面,再次到自定義的ActionFilter,最后才是例外的過濾器處理,
2、Web API的身份授權過濾器處理
我們通過AuthorizationFilterAttribute 過濾器來處理用戶Web API介面身份,比每次在代碼上進行驗證省事很多,
一般情況下,我們只要定義類繼承于AuthorizeAttribute即可,由于AuthorizeAttribute是繼承于AuthorizationFilterAttribute,如下所示,
/// <summary> /// 驗證Web Api介面用戶身份 /// </summary> public class ApiAuthorizeAttribute : AuthorizeAttribute { ........... }

而一般情況下,我們只需要重寫bool IsAuthorized(HttpActionContext actionContext) 方法,實作授權處理邏輯即可,

我們在CheckToken的主要邏輯里面,主要對token令牌進行反向決議,并判斷用戶身份是否符合,如果不符合拋出例外,就會切換到例外處理器里面了,

然后在Web API控制器中,需要授權訪問的Api控制器定義即可,我們可以把它放到基類里面宣告這個過濾器特性,

那么所有Api介面的訪問,都會檢查用戶的身份了,
2、自定義過濾器特性ActionFilterAttribute 的處理
這個ActionFilterAttribute 主要用于攔截用戶訪問控制器方法的處理程序,前面說到,OnActionExecuting方法在Action執行之前執行,OnActionExecuted方法在Action執行完成之后執行,
那么我們可以利用它進行函式AOP的處理了,也就是在執行前,執行后進行日志記錄等,還有就是常規的引數檢查、結果封裝等,都可以在這個自定義過濾器中實作,
我們定義一個類WrapResultAttribute來標記封裝結果,定義一個類DontWrapResultAttribute來標記不封裝結果,
/// <summary> /// 用于判斷Web API需要包裝回傳結果. /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)] public class WrapResultAttribute : ActionFilterAttribute { } /// <summary> /// 用于判斷Web API不需要包裝回傳結果. /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)] public class DontWrapResultAttribute : WrapResultAttribute { }
這個處理方式是借用ABP框架中這兩個特性的處理邏輯,
利用,對于獲取用戶身份令牌的基礎操作介面,我們可以不封裝回傳結果,如下標記所示,

那么執行后,回傳的結果如下所示,就是正常的TokenResult物件的JSON資訊
{ "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxIiwiaWF0IjoxNjE3MjY0MDQ4LCJqdGkiOiI0NTBjZmY3OC01OTEwLTQwYzUtYmJjMC01OTQ0YzNjMjhjNTUiLCJuYW1lIjoiYWRtaW4iLCJjb3JwaWQiOiI2IiwiY2hhbm5lbCI6IjAiLCJzaGFyZWRrZXkiOiIxMjM0YWJjZCJ9.Umv4j80Sj6BnoCCGO5LrnyddwtfqU5a8Jii92SjPApw", "expires_in": 604800 }
如果取消這個DontWrapResult的標記,那么它就繼承基類BaseApiController的WrapResult的標記定義了,
/// <summary> /// 所有介面基類 /// </summary> [ExceptionHandling] [WrapResult] public class BaseApiController : ApiController
那么介面定義不變,但是回傳的okenResult物件的JSON資訊已經經過包裝了,
{ "result": { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxIiwiaWF0IjoxNjE3MjY0NDQ5LCJqdGkiOiJmZTAzYzhlNi03NGVjLTRjNmEtYmMyZi01NTU3MjFiOTM1NDEiLCJuYW1lIjoiYWRtaW4iLCJjb3JwaWQiOiI2IiwiY2hhbm5lbCI6IjAiLCJzaGFyZWRrZXkiOiIxMjM0YWJjZCJ9.9B4dyoE9YTisl36A-w_evLs2o8raopwvDUIr2LxhO1c", "expires_in": 604800 }, "targetUrl": null, "success": true, "error": null, "unAuthorizedRequest": false, "__api": true }
這個JSON格式是我們一個通用的介面回傳,其中Result里面定義了回傳的資訊,而Error里面則定義一些錯誤資訊(如果有錯誤的話),而success則用于判斷是否執行成功,如果有錯誤例外資訊,那么success回傳為false,
這個通用回傳的定義,是依照ABP框架的回傳格式進行調整的,可以作為我們普通Web API的一個通用回傳結果的處理,
前面提到過ActionFilterAttribute自定義處理程序,在控制器方法完成后,我們對回傳的結果進行進一步的封裝處理即可,
我們需要重寫邏輯實作OnActionExecuted的函式
在做包裝回傳結果之前,我們需要判斷是否方法或者控制器設定了不包裝的標記DontWrapResultAttribute,
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { //如果有例外,則退出交給Exception的通用處理 if (actionExecutedContext.Exception != null) return; //正常完成,那么判斷是否需要包裝結果輸出,如果不需要則回傳 var dontWrap = false; var actionContext = actionExecutedContext.ActionContext; if (actionContext.ActionDescriptor is ReflectedHttpActionDescriptor actionDesc) { //判斷方法是否包含DontWrapResultAttribute dontWrap = actionDesc.MethodInfo.GetCustomAttributes(inherit: false) .Any(a => a.GetType().Equals(typeof(DontWrapResultAttribute))); if (dontWrap) return; } if (actionContext.ControllerContext.ControllerDescriptor is HttpControllerDescriptor controllerDesc) { //判斷控制器是否包含DontWrapResultAttribute dontWrap = controllerDesc.GetCustomAttributes<Attribute>(inherit: true) .Any(a => a.GetType().Equals(typeof(DontWrapResultAttribute))); if (dontWrap) return; }
上述代碼也就是如果找到方法或者控制器有定義DontWrapResultAttribute的,就不要包裝結果,否則下一步就是對結果進行封裝了
//需要包裝,那么就包裝輸出結果 AjaxResponse result = new AjaxResponse(); // 狀態代碼 var statusCode = actionContext.Response.StatusCode; // 讀取回傳的內容 var content = actionContext.Response.Content.ReadAsAsync<object>().Result; // 請求是否成功 result.Success = actionContext.Response.IsSuccessStatusCode; // 重新封裝回傳格式 actionExecutedContext.Response = new HttpResponseMessage(statusCode) { Content = new ObjectContent<AjaxResponse>( new AjaxResponse(content), JsonFomatterHelper.GetFormatter()) };
其中AjaxResponse是參考ABP框架里面回傳結果的類定義處理的,
public abstract class AjaxResponseBase { public string TargetUrl { get; set; } public bool Success { get; set; } public ErrorInfo Error { get; set; } public bool UnAuthorizedRequest { get; set; } public bool __api { get; } = true; }
[Serializable] public class AjaxResponse<TResult> : AjaxResponseBase { public TResult Result { get; set; } }
3、例外處理過濾器ExceptionFilterAttribute
前面介紹到,Web API的過濾器無法改變其順序,它是按照 AuthorizationFilterAttribute -> ActionFilterAttribute -> ExceptionFilterAttribute 這個執行順序來處理的,也就是說授權過濾器執行在前面,再次到自定義的ActionFilter,最后才是例外的過濾器處理,
例外處理過濾器,我們定義后,可以統一處理和封裝例外資訊,而我們只需要實作OnException的方法即可,
/// <summary> /// 自定義例外處理 /// </summary> public class ExceptionHandlingAttribute : ExceptionFilterAttribute { /// <summary> /// 統一對呼叫例外資訊進行處理,回傳自定義的例外資訊 /// </summary> /// <param name="context">HTTP背景關系物件</param> public override void OnException(HttpActionExecutedContext context) { } }
完整的處理程序代碼如下所示,
/// <summary> /// 自定義例外處理 /// </summary> public class ExceptionHandlingAttribute : ExceptionFilterAttribute { /// <summary> /// 統一對呼叫例外資訊進行處理,回傳自定義的例外資訊 /// </summary> /// <param name="context">HTTP背景關系物件</param> public override void OnException(HttpActionExecutedContext context) { //獲取方法或控制器對應的WrapResultAttribute屬性 var actionDescriptor = context.ActionContext.ActionDescriptor; var wrapResult = actionDescriptor.GetCustomAttributes<WrapResultAttribute>(inherit: true).FirstOrDefault() ?? actionDescriptor.ControllerDescriptor.GetCustomAttributes<WrapResultAttribute>(inherit: true).FirstOrDefault(); //如設定,記錄例外資訊 if (wrapResult != null && wrapResult.LogError) { LogHelper.Error(context.Exception); } var statusCode = GetStatusCode(context, wrapResult.WrapOnError); if (!wrapResult.WrapOnError) { context.Response = new HttpResponseMessage(statusCode) { Content = new StringContent(context.Exception.Message.ToJson()) }; context.Exception = null; //Handled! return; } //使用AjaxResponse包裝結果 var content = new ErrorInfo(context.Exception.Message/*, context.Exception.StackTrace*/); var isAuth = context.Exception is AuthorizationException; context.Response = new HttpResponseMessage(statusCode) { Content = new ObjectContent<AjaxResponse>( new AjaxResponse(content, isAuth), JsonFomatterHelper.GetFormatter()) }; context.Exception = null; //Handled! }
這樣我們在BaseApiController里面宣告即可,

這樣,一旦程式處理程序中,有錯誤拋出,都會統一到這里進行處理,有例外的回傳JSON如下所示,

本篇隨筆介紹利用AuthorizeAttribute實作Web API身份認證,利用ActionFilterAttribute實作對常規Web API回傳結果進行統一格式的封裝,利用ExceptionFilterAttribute實作對介面例外的統一處理,實作我們Web API常用到的幾個通用處理程序,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/271115.html
標籤:.NET技术
