概述: 

  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. Windows Live Writer 初次使用

    Windows  Live Writer 博客园文章的一款发布器,这篇文章就是通过其发布的,可以先在word中写好博客内容,直接粘贴到这里发布就OK,之前我都是先在Word中写好一份,然后在blogs ...

  2. 这里有个坑---entity为null的问题

    这里有个坑,最近加班赶个项目,忽然遇到个这个坑,先记录下来,纯当自己提高.---------每一个遇到的坑总结后都是一比财富. 我们在做项目是会使用ajax返回结果,在返回结果的时候一般选择json数 ...

  3. 爱上MVC~在Views的多级文件夹~续~分部页的支持

    回到目录 之前写的一篇文章,主要针对View视图,它可以放在N级目录下,不必须非要在views/controller/action这种关系了,而在程序运行过程中,发现分页视图对本功能并不支持,原因很简 ...

  4. 转【】浅谈sql中的in与not in,exists与not exists的区别_

    浅谈sql中的in与not in,exists与not exists的区别   1.in和exists in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表 ...

  5. 那些年我们写过的T-SQL(上篇)

    在当今这个多种不同数据库混用,各种不同语言不同框架融合的年代(一切为了降低成本并高效的提供服务),知识点多如牛毛.虽然大部分SQL脚本可以使用标准SQL来写,但在实际中,效率就是一切,因而每种不同厂商 ...

  6. 使用BCP导出导入数据

    bcp 实用工具可以在 Microsoft SQL Server 实例和用户指定格式的数据文件间大容量复制数据. 使用 bcp 实用工具可以将大量新行导入 SQL Server 表,或将表数据导出到数 ...

  7. JS在IE和FireFox之间常用函数的区别小结

    1.event.srcElement 复制代码 代码如下: //srcElement只能在IE下使用target是FireFox使用的,下面是兼容性写法 var obj = e.srcElement ...

  8. JSP网站开发基础总结《九》

    本篇属于附加篇,在之前的总结中给大家提到过一个关于登录状态验证的效果,当时是通过Session对象完成的,今天我查了一下,JSP为我们封装了一个用于过滤用的过滤器类Filter,通过它我们就可以非常轻 ...

  9. 信息加密之消息摘要算法的MAC

    MAC是消息摘要算法的第三种实现方式,另外两种方式分别为:MD2\4\5.SHA. MAC的jdk实现:1.默认密钥方式 private static void MAC_JDK(){ try { Ke ...

  10. 简明 Git 命令速查表

    本文总结了git常用的命令,以便学习者使用时查阅~   几个专用名词的译名如下 Workspace:工作区 Index / Stage:暂存区 Repository:仓库区(或本地仓库) Remote ...