基于.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 }
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的功能。
此篇到此结束,相对比较简单,欢迎大家讨论!