概述: 

  ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作。但是项目,总有异常发生,本节就来谈谈API的异常的统一处理和写统一写log逻辑的解决方案。

问题:

   在ASP.NET Web API编写时,如果每个API都写异常处理逻辑,不但加大了开发工作量,且每个开发人员处理异常返回的数据结构也不尽相同,在异常发生情况下,客户端处理异常的逻辑就不再通用,也同时加大了对接接口人员的工作量,好的API错误码和错误信息都是固定格式,并后台应该有相应的异常记录。

异常的统一处理的实现:

1. 首先定义异常处理Attribute,继承System.Web.Http.Filters.ExceptionAttribute, 重写OnException, 代码如下

   public class ErrorHandleAttribute : System.Web.Http.Filters.ExceptionFilterAttribute
{
private string _msg = string.Empty; public ErrorHandleAttribute() { } public ErrorHandleAttribute(string msg)
{
this._msg = msg;
}
public override void OnException(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
base.OnException(actionExecutedContext);
// 取得发生异常时的错误讯息
//var errorMessage = actionExecutedContext.Exception.Message;
// 标记log
var logAction = actionExecutedContext.ActionContext.ActionDescriptor.GetCustomAttributes<NoErrorHandlerAttribute>();
if (logAction.Any())
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError, new ResultData(ResultType.SystemException, actionExecutedContext.Exception.Message));
return;
} var request = HttpContext.Current.Request;
var logDetail = new LogDetail
{
//获取action名称
ActionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName,
//获取Controller 名称
ControllerName = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName,
Navigator = request.UserAgent,
//获取访问的ip
IP = request.UserHostAddress,
UserHostName = request.UserHostName,
UrlReferrer = request.UrlReferrer != null ? request.UrlReferrer.AbsoluteUri : "",
Browser = request.Browser.Browser + " - " + request.Browser.Version + " - " + request.Browser.Type,
//获取request提交的参数
Paramaters = GetRequestValues(actionExecutedContext),
//获取response响应的结果
//ExecuteResult = GetResponseValues(actionExecutedContext), //这句会报错,异常没有处理结果
AttrTitle = this._msg,
ErrorMsg = string.Format("错误信息:{0}, 异常跟踪:{1}", actionExecutedContext.Exception.Message, actionExecutedContext.Exception.StackTrace),
RequestUri = request.Url.AbsoluteUri
}; // 写log
var logRep = ContainerManager.Resolve<ISysLogRepository>();
var log = new Log()
{
Action = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName + "/" + actionExecutedContext.ActionContext.ActionDescriptor.ActionName,
CreateDate = DateTime.Now,
CreatorLoginName = RISContext.Current.CurrentUserInfo.UserName,
IpAddress = request.UserHostAddress,
Detail = Utility.JsonSerialize<LogDetail>(logDetail)
}; logRep.Add(log);
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(System.Net.HttpStatusCode.InternalServerError, new ResultData(ResultType.SystemException, actionExecutedContext.Exception.Message));
} /// <summary>
/// 读取request 的提交内容
/// </summary>
/// <param name="actionExecutedContext"></param>
/// <returns></returns>
public string GetRequestValues(HttpActionExecutedContext actionExecutedContext)
{ Stream stream = actionExecutedContext.Request.Content.ReadAsStreamAsync().Result;
Encoding encoding = Encoding.UTF8;
/*
这个StreamReader不能关闭,也不能dispose, 关了就傻逼了
因为你关掉后,后面的管道 或拦截器就没办法读取了
*/
var reader = new StreamReader(stream, encoding);
string result = reader.ReadToEnd();
/*
这里也要注意: stream.Position = 0;
当你读取完之后必须把stream的位置设为开始
因为request和response读取完以后Position到最后一个位置,交给下一个方法处理的时候就会读不到内容了。
*/
stream.Position = ;
return result;
}
}

2. 接下来定义不需要异常处理的Attribute,代码如下:

     [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
public class NoErrorHandlerAttribute : Attribute
{
}

3. 在HttpConfiguration中注册使用 ErrorHandleAttribute, 注册代码如下:

config.Filters.Add(new ErrorHandleAttribute("错误处理"));

一般在项目的WebApiConfig.cs中注册此属性:

  /// <summary>
/// WebApiConfig
/// </summary>
public static class WebApiConfig
{
/// <summary>
/// WebApiConfig Register
/// </summary>
/// <param name="config"></param>
public static void Register(HttpConfiguration config)
{
//config.Filters.Add(new TokenAuthorizeAttribute());
config.MessageHandlers.Add(new CrosHandler());
config.Filters.Add(new ApiAuthorizeAttribute());
config.Filters.Add(new ErrorHandleAttribute("错误处理"));
// Web API 路由
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "mobileapi/{controller}/{action}/{id}",
defaults: new { controller = "Test", action = "GetTestValue", id = RouteParameter.Optional }
);
}
}

这样就可以了,在每个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,代码如下:

  public class LogAttribute : ActionFilterAttribute
{
private string _msg = string.Empty;
private string _token = string.Empty;
private string _remark = string.Empty;
public LogAttribute() { } public LogAttribute(string msg)
{
this._msg = msg;
} //http://www.cnblogs.com/shan333chao/p/5002054.html
private static readonly string key = "enterTime";
private const string UserToken = "token";
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.Method != HttpMethod.Options)
{
// 标记log
var logAction = actionContext.ActionDescriptor.GetCustomAttributes<NoLogAttribute>();
if (!logAction.Any())
{
actionContext.Request.Properties[key] = DateTime.Now.ToBinary();
this._token = GetToken(actionContext, out this._remark);
}
}
base.OnActionExecuting(actionContext);
} public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Request.Method != HttpMethod.Options)
{
object beginTime = null;
if (actionExecutedContext.Request.Properties.TryGetValue(key, out beginTime))
{
DateTime time = DateTime.FromBinary(Convert.ToInt64(beginTime));
var request = HttpContext.Current.Request;
var logDetail = new LogDetail
{
//获取action名称
ActionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName,
//获取Controller 名称
ControllerName = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName,
//获取action开始执行的时间
EnterTime = time,
//获取执行action的耗时
CostTime = (DateTime.Now - time).TotalMilliseconds,
Navigator = request.UserAgent,
Token = this._token,
//获取用户ID
UId = UserTokenManager.GetUId(this._token),
//获取访问的ip
IP = request.UserHostAddress,
UserHostName = request.UserHostName,
UrlReferrer = request.UrlReferrer != null ? request.UrlReferrer.AbsoluteUri : "",
Browser = request.Browser.Browser + " - " + request.Browser.Version + " - " + request.Browser.Type,
//获取request提交的参数
Paramaters = GetRequestValues(actionExecutedContext),
//获取response响应的结果
ExecuteResult = GetResponseValues(actionExecutedContext),
AttrTitle = this._msg,
Remark = this._remark,
RequestUri = request.Url.AbsoluteUri
}; // 登录log
var logRep = ContainerManager.Resolve<ISysLogRepository>();
var log = new Log()
{
Action = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName + "/" + actionExecutedContext.ActionContext.ActionDescriptor.ActionName,
CreateDate = DateTime.Now,
CreatorLoginName = RISContext.Current.CurrentUserInfo.UserName,
IpAddress = request.UserHostAddress,
Detail = Utility.JsonSerialize<LogDetail>(logDetail)
}; logRep.Add(log);
}
} base.OnActionExecuted(actionExecutedContext);
} private string GetToken(System.Web.Http.Controllers.HttpActionContext actionContext, out string msg)
{
Dictionary<string, object> actionArguments = actionContext.ActionArguments;
HttpMethod type = actionContext.Request.Method;
msg = "";
var token = "";
if (type == HttpMethod.Post)
{
if (actionArguments.ContainsKey(UserToken))
{
if (actionArguments[UserToken] != null)
token = actionArguments[UserToken].ToString();
}
else
{
foreach (var value in actionArguments.Values)
{
if (value != null && value.GetType().GetProperty(UserToken) != null)
token = value.GetType().GetProperty(UserToken).GetValue(value, null).ToString();
}
} if (string.IsNullOrEmpty(token))
msg = "匿名用户";
}
else if (type == HttpMethod.Get)
{
if (!actionArguments.ContainsKey(UserToken))
msg = "匿名用户";
// throw new HttpException(401, "还未登录"); if (actionArguments[UserToken] != null)
token = actionArguments[UserToken].ToString();
else
msg = "匿名用户";
}
else if (type == HttpMethod.Options)
{ }
else
{
throw new HttpException(, "暂未开放除POST,GET之外的访问方式!");
}
return token;
}
/// <summary>
/// 读取request 的提交内容
/// </summary>
/// <param name="actionExecutedContext"></param>
/// <returns></returns>
public string GetRequestValues(HttpActionExecutedContext actionExecutedContext)
{ Stream stream = actionExecutedContext.Request.Content.ReadAsStreamAsync().Result;
Encoding encoding = Encoding.UTF8;
/*
这个StreamReader不能关闭,也不能dispose, 关了就傻逼了
因为你关掉后,后面的管道 或拦截器就没办法读取了
*/
var reader = new StreamReader(stream, encoding);
string result = reader.ReadToEnd();
/*
这里也要注意: stream.Position = 0;
当你读取完之后必须把stream的位置设为开始
因为request和response读取完以后Position到最后一个位置,交给下一个方法处理的时候就会读不到内容了。
*/
stream.Position = ;
return result;
} /// <summary>
/// 读取action返回的result
/// </summary>
/// <param name="actionExecutedContext"></param>
/// <returns></returns>
public string GetResponseValues(HttpActionExecutedContext actionExecutedContext)
{
Stream stream = actionExecutedContext.Response.Content.ReadAsStreamAsync().Result;
Encoding encoding = Encoding.UTF8;
/*
这个StreamReader不能关闭,也不能dispose, 关了就傻逼了
因为你关掉后,后面的管道 或拦截器就没办法读取了
*/
var reader = new StreamReader(stream, encoding);
string result = reader.ReadToEnd();
/*
这里也要注意: stream.Position = 0;
当你读取完之后必须把stream的位置设为开始
因为request和response读取完以后Position到最后一个位置,交给下一个方法处理的时候就会读不到内容了。
*/
stream.Position = ;
return result;
}
}

2. 接下来定义不需要记录log的Attribute,代码如下:

     [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
public class NoErrorHandlerAttribute : Attribute
{
}

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的实现的更多相关文章

  1. ASP.NET Web API 框架研究 ASP.NET Web API 路由

    ASP.NET Web API 核心框架是一个独立的.抽象的消息处理管道,ASP.NET Web API有自己独立的路由系统,是消息处理管道的组成部分,其与ASP.NET路由系统有类似的设计,都能找到 ...

  2. web api :Routing in ASP.NET Web API

    引 Web API 和SignalR都是在服务层. If you are familiar with ASP.NET MVC, Web API routing is very similar to M ...

  3. 水果项目第3集-asp.net web api开发入门

    app后台开发,可以用asp.net webservice技术. 也有一种重量级一点的叫WCF,也可以用来做app后台开发. 现在可以用asp.net web api来开发app后台. Asp.net ...

  4. Asp.Net Web API 2第五课——Web API路由

    Asp.Net Web API 导航   Asp.Net Web API第一课——入门 http://www.cnblogs.com/aehyok/p/3432158.html Asp.Net Web ...

  5. Implement JSON Web Tokens Authentication in ASP.NET Web API and Identity 2.1 Part 3 (by TAISEER)

    http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-an ...

  6. 循序渐进学.Net Core Web Api开发系列【0】:序言与目录

    一.序言 我大约在2003年时候开始接触到.NET,最初在.NET framework 1.1版本下写过代码,曾经做过WinForm和ASP.NET开发.大约在2010年的时候转型JAVA环境,这么多 ...

  7. ASP.NET Core Web API 开发-RESTful API实现

    ASP.NET Core Web API 开发-RESTful API实现 REST 介绍: 符合REST设计风格的Web API称为RESTful API. 具象状态传输(英文:Representa ...

  8. 循序渐进学.Net Core Web Api开发系列【9】:常用的数据库操作

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇描述一 ...

  9. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

随机推荐

  1. AWS助理架构师样题解析

    AWS 认证是对其在 AWS 平台上设计.部署和管理应用程序所需的技能和技术知识的一种认可.获得证书有助于证明您使用 AWS 的丰富经验和可信度,同时还能提升您所在的组织熟练使用基于 AWS 云服务应 ...

  2. 微软官网下载windows系统有点全

    第一步:访问:https://www.microsoft.com/zh-cn/software-download/windows10ISO/ 默认就只能下载win10,这怎么行呢.巨硬程序员貌似没做服 ...

  3. Java 9终于要包含Jigsaw项目了

    当Jigsaw在Java 9中最终发布时,这个项目的历史已经超过八年了. 转载于:http://www.itxuexiwang.com/a/liunxjishu/2016/0228/180.html? ...

  4. [SpringMVC]SpringMVC学习笔记一: springmvc原理及实例解析.

    前言:今天来回顾下SpringMVC的开发原理, 使用图文并茂的方式 来解析其中的内幕, 我相信懂了其中的运行机制后, 对于面试中SpringMVC大家都可以说so easy了. 一, 图示法 第二张 ...

  5. Atitit 图像处理 公共模块 矩阵扫描器

    Atitit 图像处理 公共模块 矩阵扫描器 1.1. 调用说明对矩阵像素遍历处理调用1 2. 矩阵扫描器主题结构1 2.1. 主要说明 从像素点开始填充矩阵1 2.2. 得到模板中心点所对应的图像坐 ...

  6. 深入分析Java Web技术内幕(修订版)

    阿里巴巴集团技术丛书 深入分析Java Web技术内幕(修订版)(阿里巴巴集团技术丛书.技术大牛范禹.玉伯.毕玄联合力荐!大型互联网公司开发应用实践!) 许令波 著   ISBN 978-7-121- ...

  7. fir.im Weekly - 如何写出零 bug 的代码

    神兽护体,代码无bug.经常看到代码注释的各种形状,这是一种程序员情怀.那么,如何能写出零 Bug 的代码呢,来看看@码农翻身 的这篇手册--零Bug的代码是怎么炼成的. 写零 Bug 一定少不了代码 ...

  8. fir.im Weekly - 热门 iOS 第三方库大盘点

    本期 fir.im Weekly 收集的热度资源,大部分关于Android.iOS 开发工具.源码和脑洞大开的 UI 动画,希望给你带来更多的工作创意与灵感. 盘点国内程序员不常用的热门iOS第三方库 ...

  9. ftp下载目录下所有文件及文件夹内(递归)

    ftp下载目录下所有文件及文件夹内(递归)   /// <summary> /// ftp文件上传.下载操作类 /// </summary> public class FTPH ...

  10. VS2013模块对于SAFESEH映像是不安全的解决方法

    常见报错:error LNK2026: 模块对于 SAFESEH 映像是不安全的 解决方法:右键打开项目属性 -> 链接器 -> 命令行 -> 其他选项 (D) 中加入  /SAFE ...