基於.Net Framework 4.0 Web API開發(3):ASP.NET Web APIs 異常的統一處理Attribute 和統一寫Log 的Attribute的實現


概述: 

  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     }
View Code

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     }
View Code

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的功能。

此篇到此結束,相對比較簡單,歡迎大家討論!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM