概述:
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 // 标记log17 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 LogDetail26 {27 //获取action名称28 ActionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName,29 //获取Controller 名称30 ControllerName = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName,31 Navigator = request.UserAgent,32 //获取访问的ip33 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.AbsoluteUri44 };45 46 // 写log47 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 : Attribute3 {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 else100 {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 else120 msg = "匿名用户";121 }122 else if (type == HttpMethod.Options)123 {124 125 }126 else127 {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返回的result159 /// </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 : Attribute3 {4 }
3. 注意不要在HttpConfiguration中注册使用 LogAttribute,除非你想所有的请求都写log,在不需要写log的Action上添加[NoLog],否则只需要在需要记录log的Action添加[Log]就可以完成写log的功能。
此篇到此结束,相对比较简单,欢迎大家讨论!
原标题:基于.Net Framework 4.0 Web API开发(3):ASP.NET Web APIs 异常的统一处理Attribute 和统一写Log 的Attribute的实现
关键词:ASP.NET