概述:
ASP.NET Web API 的好用使用過的都知道,沒有復雜的配置文件,一個簡單的ApiController加上需要的Action就能工作。但是項目,總有異常發生,本節就來談談API的異常的統一處理和寫統一寫log邏輯的解決方案。
問題:
在ASP.NET Web API編寫時,如果每個API都寫異常處理邏輯,不但加大了開發工作量,且每個開發人員處理異常返回的數據結構也不盡相同,在異常發生情況下,客戶端處理異常的邏輯就不再通用,也同時加大了對接接口人員的工作量,好的API錯誤碼和錯誤信息都是固定格式,並后台應該有相應的異常記錄。
異常的統一處理的實現:
1. 首先定義異常處理Attribute,繼承System.Web.Http.Filters.ExceptionAttribute, 重寫OnException, 代碼如下

1 public class ErrorHandleAttribute : System.Web.Http.Filters.ExceptionFilterAttribute 2 { 3 private string _msg = string.Empty; 4 5 public ErrorHandleAttribute() { } 6 7 public ErrorHandleAttribute(string msg) 8 { 9 this._msg = msg; 10 } 11 public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext) 12 { 13 base.OnException(actionExecutedContext); 14 // 取得發生異常時的錯誤訊息 15 //var errorMessage = actionExecutedContext.Exception.Message; 16 // 標記log 17 var logAction = actionExecutedContext.ActionContext.ActionDescriptor.GetCustomAttributes<NoErrorHandlerAttribute>(); 18 if (logAction.Any()) 19 { 20 actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError, new ResultData(ResultType.SystemException, actionExecutedContext.Exception.Message)); 21 return; 22 } 23 24 var request = HttpContext.Current.Request; 25 var logDetail = new LogDetail 26 { 27 //獲取action名稱 28 ActionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName, 29 //獲取Controller 名稱 30 ControllerName = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName, 31 Navigator = request.UserAgent, 32 //獲取訪問的ip 33 IP = request.UserHostAddress, 34 UserHostName = request.UserHostName, 35 UrlReferrer = request.UrlReferrer != null ? request.UrlReferrer.AbsoluteUri : "", 36 Browser = request.Browser.Browser + " - " + request.Browser.Version + " - " + request.Browser.Type, 37 //獲取request提交的參數 38 Paramaters = GetRequestValues(actionExecutedContext), 39 //獲取response響應的結果 40 //ExecuteResult = GetResponseValues(actionExecutedContext), //這句會報錯,異常沒有處理結果 41 AttrTitle = this._msg, 42 ErrorMsg = string.Format("錯誤信息:{0}, 異常跟蹤:{1}", actionExecutedContext.Exception.Message, actionExecutedContext.Exception.StackTrace), 43 RequestUri = request.Url.AbsoluteUri 44 }; 45 46 // 寫log 47 var logRep = ContainerManager.Resolve<ISysLogRepository>(); 48 var log = new Log() 49 { 50 Action = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName + "/" + actionExecutedContext.ActionContext.ActionDescriptor.ActionName, 51 CreateDate = DateTime.Now, 52 CreatorLoginName = RISContext.Current.CurrentUserInfo.UserName, 53 IpAddress = request.UserHostAddress, 54 Detail = Utility.JsonSerialize<LogDetail>(logDetail) 55 }; 56 57 logRep.Add(log); 58 actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError, new ResultData(ResultType.SystemException, actionExecutedContext.Exception.Message)); 59 } 60 61 /// <summary> 62 /// 讀取request 的提交內容 63 /// </summary> 64 /// <param name="actionExecutedContext"></param> 65 /// <returns></returns> 66 public string GetRequestValues(HttpActionExecutedContext actionExecutedContext) 67 { 68 69 Stream stream = actionExecutedContext.Request.Content.ReadAsStreamAsync().Result; 70 Encoding encoding = Encoding.UTF8; 71 /* 72 這個StreamReader不能關閉,也不能dispose, 關了就傻逼了 73 因為你關掉后,后面的管道 或攔截器就沒辦法讀取了 74 */ 75 var reader = new StreamReader(stream, encoding); 76 string result = reader.ReadToEnd(); 77 /* 78 這里也要注意: stream.Position = 0; 79 當你讀取完之后必須把stream的位置設為開始 80 因為request和response讀取完以后Position到最后一個位置,交給下一個方法處理的時候就會讀不到內容了。 81 */ 82 stream.Position = 0; 83 return result; 84 } 85 }
2. 接下來定義不需要異常處理的Attribute,代碼如下:
1 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] 2 public class NoErrorHandlerAttribute : Attribute 3 { 4 }
3. 在HttpConfiguration中注冊使用 ErrorHandleAttribute, 注冊代碼如下:
config.Filters.Add(new ErrorHandleAttribute("錯誤處理"));
一般在項目的WebApiConfig.cs中注冊此屬性:
1 /// <summary> 2 /// WebApiConfig 3 /// </summary> 4 public static class WebApiConfig 5 { 6 /// <summary> 7 /// WebApiConfig Register 8 /// </summary> 9 /// <param name="config"></param> 10 public static void Register(HttpConfiguration config) 11 { 12 //config.Filters.Add(new TokenAuthorizeAttribute()); 13 config.MessageHandlers.Add(new CrosHandler()); 14 config.Filters.Add(new ApiAuthorizeAttribute()); 15 config.Filters.Add(new ErrorHandleAttribute("錯誤處理")); 16 // Web API 路由 17 config.Routes.MapHttpRoute( 18 name: "DefaultApi", 19 routeTemplate: "mobileapi/{controller}/{action}/{id}", 20 defaults: new { controller = "Test", action = "GetTestValue", id = RouteParameter.Optional } 21 ); 22 } 23 }
這樣就可以了,在每個Action中就不要寫try catch了,否則不執行ErrorHandle中異常處理邏輯
4. 如果特殊的Controller或者Action不需要紀錄和處理異常,可以在Controller或者Action上添加[NoErrorHandler],這樣就不會執行ErrorHandle中異常處理邏輯
以上部分是異常的統一處理邏輯, 接下來實現統一寫Log的 Attribute功能
統一寫Log的 Attribute功能實現:
1. 首先定義寫Log的Attribute,繼承System.Web.Http.Filters.ActionFilterAttribute,重寫OnActionExecuting和OnActionExecuted,代碼如下:

1 public class LogAttribute : ActionFilterAttribute 2 { 3 private string _msg = string.Empty; 4 private string _token = string.Empty; 5 private string _remark = string.Empty; 6 public LogAttribute() { } 7 8 public LogAttribute(string msg) 9 { 10 this._msg = msg; 11 } 12 13 //http://www.cnblogs.com/shan333chao/p/5002054.html 14 private static readonly string key = "enterTime"; 15 private const string UserToken = "token"; 16 public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) 17 { 18 if (actionContext.Request.Method != HttpMethod.Options) 19 { 20 // 標記log 21 var logAction = actionContext.ActionDescriptor.GetCustomAttributes<NoLogAttribute>(); 22 if (!logAction.Any()) 23 { 24 actionContext.Request.Properties[key] = DateTime.Now.ToBinary(); 25 this._token = GetToken(actionContext, out this._remark); 26 } 27 } 28 base.OnActionExecuting(actionContext); 29 } 30 31 public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 32 { 33 if (actionExecutedContext.Request.Method != HttpMethod.Options) 34 { 35 object beginTime = null; 36 if (actionExecutedContext.Request.Properties.TryGetValue(key, out beginTime)) 37 { 38 DateTime time = DateTime.FromBinary(Convert.ToInt64(beginTime)); 39 var request = HttpContext.Current.Request; 40 var logDetail = new LogDetail 41 { 42 //獲取action名稱 43 ActionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName, 44 //獲取Controller 名稱 45 ControllerName = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName, 46 //獲取action開始執行的時間 47 EnterTime = time, 48 //獲取執行action的耗時 49 CostTime = (DateTime.Now - time).TotalMilliseconds, 50 Navigator = request.UserAgent, 51 Token = this._token, 52 //獲取用戶ID 53 UId = UserTokenManager.GetUId(this._token), 54 //獲取訪問的ip 55 IP = request.UserHostAddress, 56 UserHostName = request.UserHostName, 57 UrlReferrer = request.UrlReferrer != null ? request.UrlReferrer.AbsoluteUri : "", 58 Browser = request.Browser.Browser + " - " + request.Browser.Version + " - " + request.Browser.Type, 59 //獲取request提交的參數 60 Paramaters = GetRequestValues(actionExecutedContext), 61 //獲取response響應的結果 62 ExecuteResult = GetResponseValues(actionExecutedContext), 63 AttrTitle = this._msg, 64 Remark = this._remark, 65 RequestUri = request.Url.AbsoluteUri 66 }; 67 68 // 登錄log 69 var logRep = ContainerManager.Resolve<ISysLogRepository>(); 70 var log = new Log() 71 { 72 Action = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName + "/" + actionExecutedContext.ActionContext.ActionDescriptor.ActionName, 73 CreateDate = DateTime.Now, 74 CreatorLoginName = RISContext.Current.CurrentUserInfo.UserName, 75 IpAddress = request.UserHostAddress, 76 Detail = Utility.JsonSerialize<LogDetail>(logDetail) 77 }; 78 79 logRep.Add(log); 80 } 81 } 82 83 base.OnActionExecuted(actionExecutedContext); 84 } 85 86 private string GetToken(System.Web.Http.Controllers.HttpActionContext actionContext, out string msg) 87 { 88 Dictionary<string, object> actionArguments = actionContext.ActionArguments; 89 HttpMethod type = actionContext.Request.Method; 90 msg = ""; 91 var token = ""; 92 if (type == HttpMethod.Post) 93 { 94 if (actionArguments.ContainsKey(UserToken)) 95 { 96 if (actionArguments[UserToken] != null) 97 token = actionArguments[UserToken].ToString(); 98 } 99 else 100 { 101 foreach (var value in actionArguments.Values) 102 { 103 if (value != null && value.GetType().GetProperty(UserToken) != null) 104 token = value.GetType().GetProperty(UserToken).GetValue(value, null).ToString(); 105 } 106 } 107 108 if (string.IsNullOrEmpty(token)) 109 msg = "匿名用戶"; 110 } 111 else if (type == HttpMethod.Get) 112 { 113 if (!actionArguments.ContainsKey(UserToken)) 114 msg = "匿名用戶"; 115 // throw new HttpException(401, "還未登錄"); 116 117 if (actionArguments[UserToken] != null) 118 token = actionArguments[UserToken].ToString(); 119 else 120 msg = "匿名用戶"; 121 } 122 else if (type == HttpMethod.Options) 123 { 124 125 } 126 else 127 { 128 throw new HttpException(404, "暫未開放除POST,GET之外的訪問方式!"); 129 } 130 return token; 131 } 132 /// <summary> 133 /// 讀取request 的提交內容 134 /// </summary> 135 /// <param name="actionExecutedContext"></param> 136 /// <returns></returns> 137 public string GetRequestValues(HttpActionExecutedContext actionExecutedContext) 138 { 139 140 Stream stream = actionExecutedContext.Request.Content.ReadAsStreamAsync().Result; 141 Encoding encoding = Encoding.UTF8; 142 /* 143 這個StreamReader不能關閉,也不能dispose, 關了就傻逼了 144 因為你關掉后,后面的管道 或攔截器就沒辦法讀取了 145 */ 146 var reader = new StreamReader(stream, encoding); 147 string result = reader.ReadToEnd(); 148 /* 149 這里也要注意: stream.Position = 0; 150 當你讀取完之后必須把stream的位置設為開始 151 因為request和response讀取完以后Position到最后一個位置,交給下一個方法處理的時候就會讀不到內容了。 152 */ 153 stream.Position = 0; 154 return result; 155 } 156 157 /// <summary> 158 /// 讀取action返回的result 159 /// </summary> 160 /// <param name="actionExecutedContext"></param> 161 /// <returns></returns> 162 public string GetResponseValues(HttpActionExecutedContext actionExecutedContext) 163 { 164 Stream stream = actionExecutedContext.Response.Content.ReadAsStreamAsync().Result; 165 Encoding encoding = Encoding.UTF8; 166 /* 167 這個StreamReader不能關閉,也不能dispose, 關了就傻逼了 168 因為你關掉后,后面的管道 或攔截器就沒辦法讀取了 169 */ 170 var reader = new StreamReader(stream, encoding); 171 string result = reader.ReadToEnd(); 172 /* 173 這里也要注意: stream.Position = 0; 174 當你讀取完之后必須把stream的位置設為開始 175 因為request和response讀取完以后Position到最后一個位置,交給下一個方法處理的時候就會讀不到內容了。 176 */ 177 stream.Position = 0; 178 return result; 179 } 180 }
2. 接下來定義不需要記錄log的Attribute,代碼如下:
1 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] 2 public class NoErrorHandlerAttribute : Attribute 3 { 4 }
3. 注意不要在HttpConfiguration中注冊使用 LogAttribute,除非你想所有的請求都寫log,在不需要寫log的Action上添加[NoLog],否則只需要在需要記錄log的Action添加[Log]就可以完成寫log的功能。
此篇到此結束,相對比較簡單,歡迎大家討論!