Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 427|回复: 0

基于.Net Framework 4.0 Web API开发(3):ASP.NET Web APIs 异常的统一处理Attribute 和统一写Log 的Attribute的实现

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-5-25 07:08:00 | 显示全部楼层 |阅读模式

    概述: 

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

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

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-18 14:43 , Processed in 0.069265 second(s), 30 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表