你的位置:首页 > ASP.net教程

[ASP.net教程]基于.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       // 标记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的功能。

此篇到此结束,相对比较简单,欢迎大家讨论!