基于.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, 代码如下
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的实现的更多相关文章
- ASP.NET Web API 框架研究 ASP.NET Web API 路由
ASP.NET Web API 核心框架是一个独立的.抽象的消息处理管道,ASP.NET Web API有自己独立的路由系统,是消息处理管道的组成部分,其与ASP.NET路由系统有类似的设计,都能找到 ...
- 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集-asp.net web api开发入门
app后台开发,可以用asp.net webservice技术. 也有一种重量级一点的叫WCF,也可以用来做app后台开发. 现在可以用asp.net web api来开发app后台. Asp.net ...
- 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 ...
- 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 ...
- 循序渐进学.Net Core Web Api开发系列【0】:序言与目录
一.序言 我大约在2003年时候开始接触到.NET,最初在.NET framework 1.1版本下写过代码,曾经做过WinForm和ASP.NET开发.大约在2010年的时候转型JAVA环境,这么多 ...
- ASP.NET Core Web API 开发-RESTful API实现
ASP.NET Core Web API 开发-RESTful API实现 REST 介绍: 符合REST设计风格的Web API称为RESTful API. 具象状态传输(英文:Representa ...
- 循序渐进学.Net Core Web Api开发系列【9】:常用的数据库操作
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇描述一 ...
- 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入
使用react全家桶制作博客后台管理系统 前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...
随机推荐
- HTML5本地存储——IndexedDB(二:索引)
在HTML5本地存储——IndexedDB(一:基本使用)中介绍了关于IndexedDB的基本使用方法,很不过瘾,这篇我们来看看indexedDB的杀器——索引. 熟悉数据库的同学都知道索引的一个好处 ...
- 每天一个linux命令(54):ping命令
Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”.不能打开网页时会说“你先ping网关地址192.168.1.1试试”. ...
- MVVM架构~knockoutjs系列之从Knockout.Validation.js源码中学习它的用法
返回目录 说在前 有时,我们在使用一个插件时,在网上即找不到它的相关API,这时,我们会很抓狂的,与其抓狂,还不如踏下心来,分析一下它的源码,事实上,对于JS这种开发语言来说,它开发的插件的使用方法都 ...
- iOS----Asset Catalog的用法
文/余书懿(简书作者)原文链接:http://www.jianshu.com/p/7aa06ce22a7b著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 引言 Asset Catalo ...
- iOS---------- @synchronized(self)的用法
1. synchronized 这个主要是考虑多线程的程序,这个指令可以将{ } 内的代码限制在一个线程执行,如果某个线程没有执行完,其他的线程如果需要执行就得等着. Objective-C除了提 ...
- iOS-Delegate模式
代理模式 顾名思义就是委托别人去做事情. IOS中经常会遇到的两种情况:在cocoa框架中的Delegate模式与自定义的委托模式.下面分别举例说明一下: 一.cocoa框架中的delegate模式 ...
- 利用奇异值分解(SVD)简化数据
特征值与特征向量 下面这部分内容摘自:强大的矩阵奇异值分解(SVD)及其应用 特征值分解和奇异值分解在机器学习领域都是属于满地可见的方法.两者有着很紧密的关系,在接下来会谈到,特征值分解和奇异值分解的 ...
- CCNA网络工程师学习进程(6)vlan相关协议的配置与路由器简单配置介绍
前面已经介绍了大部分与vlan技术相关的交换机的协议的配置,更深层次的还有STP协议和以太网端口聚合技术,接着还会简单介绍一下路由器的基本应用. (1)STP(Spanning-tre ...
- CSS拉伸resize
CSS3新增了resize属性,该属性允许用户通过拖动的方式来修改元素的尺寸.本来resize应该翻译为缩放,但在实际测试中通过resize属性只可以在宽高基础上实现拉伸效果,而无法实现缩放到小于宽高 ...
- Cwinux源码解析系列
Cwinux源码解析系列