上一篇我们完善了多层开发的效率问题,传送门:
这次我们完成架构的异常处理功能,异常处理一般都与日志分不开的,因为分析及定位问题需要一些详细信息;
稍微正规一点的公司,都会分开发、测试及生产环境。在本地及测试环境出 BUG 了,问题很好解决
调试跟踪问题,三下五除二就搞完了;但是在生产环境出问题,基本上是不允许直连数据库调试的;
这时候如何没有足够的异常信息参考,那你就悲催了,你等着加班熬夜吧。
为了解决这个问题,所以异常信息的捕捉及记录就显得非常重要了,一个完善的系统,出问题后不可能要去调试才能知道具体原因的
1.1 其实 ASP.NET MVC 已经支持全局异常的处理,就是这个:HandleErrorAttribute,这里我们只是简单介绍他的使用方法
详情可以看看这篇文章:,下面我们一步步来。
FilterConfig.cs,这是系统默认生成的
- 1 using System.web;
- 2 using System.Web.Mvc;
- 3
- 4 namespace Presentation.MVC
- 5 {
- 6 public class FilterConfig
- 7 {
- 8 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
- 9 {
- 10 filters.Add(new HandleErrorAttribute());
- 11 }
- 12 }
- 13 }
1.2 要在 Web.config 中开启 customErrors,不然没有效果
- 1 <customErrors mode="On" defaultRedirect="~/Error/Index">
- 2 </customErrors>
1.3 设置好后,系统发生异常后会自动跳转到默认的 Error.cshtml 界面
- 1 <div class="container">
- 2 <h1 class="text-danger">错误。</h1>
- 3 <h2 class="text-danger">处理你的请求时出错。</h2>
- 4
- 5 @if (Model != null)
- 6 {
- 7 <p class="bg-danger text-danger">
- 8 异常类型:@Model.Exception.GetType().Name
- 9 </p>
- 10 <p class="bg-danger text-danger">
- 11 触发异常的控制器:@Model.ControllerName
- 12 </p>
- 13 <p class="bg-danger text-danger">
- 14 触发异常的操作方法:@Model.ActionName
- 15 </p>
- 16 <p class="bg-danger text-danger">
- 17 错误信息:@Model.Exception.Message
- 18 </p>
- 19 <p class="bg-info text-info">
- 20 页面路径:~/Views/Shared/Error.cshtml
- 21 </p>
- 22 }
- 23 </div>
1.4 我们再 Home/Index 初始页触发一个异常试试看
- 1 public ActionResult Login()
- 2 {
- 3 string str = null;
- 4 str.GetType();//空引用
- 5
- 6 return View();
- 7 }
可以看到已经跳转到默认错误显示页面了
但是这样是不够的,一般这个页面会美化,客户端用户会看到更加友好的提示信息
而且这里并没有异常堆栈,看不到异常的具体信息,这样定位问题就困难;
所以还需要加工一下,我们设计一个自定义的异常处理类
2.1 LjrExecptionAttribute.cs,很简单啊,就不解释了
- 1 using Infrastructure.Core;
- 2 using System.Web.Mvc;
- 3
- 4 namespace Presentation.MVC
- 5 {
- 6 public class LjrExecptionAttribute : HandleErrorAttribute
- 7 {
- 8 public override void OnException(ExceptionContext filterContext)
- 9 {
- 10 Logger.Error(filterContext.Exception.Message, filterContext.Exception);
- 11
- 12 base.OnException(filterContext);
- 13 }
- 14 }
- 15 }
2.2 然后 FilterConfig.cs 要改一下
- 1 public class FilterConfig
- 2 {
- 3 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
- 4 {
- 5 //filters.Add(new HandleErrorAttribute());
- 6 filters.Add(new LjrExecptionAttribute());
- 7 }
- 8 }
2.3 web.config 也改一下,因为 HandleErrorAttribute 处理不了 HTTP404
- 1 <customErrors mode="On" defaultRedirect="~/Error/Index">
- 2 <error redirect="~/Error/NotFound" statusCode="404" />
- 3 </customErrors>
2.4 新建 ErrorController.cs
- 1 public class ErrorController: Controller 2 {
- 3 public ActionResult Index() 4 {
- 5
- return View();
- 6
- }
- 7 8 public ActionResult NotFound() 9 {
- 10
- return View();
- 11
- }
- 12
- }
2.5 NotFound.cshtml
- 1 @{
- 2 Layout = null;
- 3 }
- 4
- 5 <div style=" margin:0px auto; width:500px; margin:20px;">
- 6 <h2>NotFound</h2>
- 7 一般人看不出来,这是一个美化了的NotFound页面。
- 8 </div>
2.6 Error.cshtml 改得更友好一些
- 1 <div style=" margin:0px auto; width:500px; margin:20px;">
- 2 <h2>默认异常页面</h2>
- 3 你好,这是系统默认异常界面,已经美化过了,请放心使用。
- 4 </div>
2.7 在 Home 控制器中手动触发异常
- 1 public ActionResult ThrowHttp500()
- 2 {
- 3 throw new HttpException(500, "服务器错误");
- 4 }
- 5
- 6 public ActionResult ThrowHttp404()
- 7 {
- 8 throw new HttpException(404, "页面未找到");
- 9 }
- 10
- 11 [HandleError(ExceptionType = typeof(NullReferenceException))]
- 12 public ActionResult ThrowNullReferenceException()
- 13 {
- 14 throw new NullReferenceException();
- 15 }
- 16
- 17 public ActionResult ThrowFormatException()
- 18 {
- 19 string str = "";
- 20 int count = Convert.ToInt32(str);
- 21 return View("Index");
- 22 }
2.8 运行以下几种异常会跳到之前的默认异常页面
http://localhost:5572/Home/ThrowHttp500
http://localhost:5572/Home/ThrowNullReferenceException
http://localhost:5572/Home/ThrowFormatException
2.9 页面未找到会转至:http://localhost:5572/Home/ThrowHttp404
2.10 当然了,别忘了在 2.2 中我们还在中记录了日志功能(LjrExecptionAttribute),我们去看看
堆栈信息都有了,这就很好定位 BUG 位置了,通过分析其日志,大概可以知道问题原因
上边已经解决了 WEB 中的异常信息,但是一个项目不可能只有 WEB,还有很多类库,WebService 等
3.1 看看捕捉异常的一般做法
- 1 public bool CommonMethod(LoginUserCURequest entity)
- 2 {
- 3 try
- 4 {
- 5 this.repository.Add(new LoginUser()
- 6 {
- 7 Id = entity.Id,
- 8 LoginName = entity.LoginName,
- 9 Password = entity.Password,
- 10 IsEnabled = entity.IsEnabled,
- 11 CreateTime = DateTime.Now
- 12 });
- 13
- 14 foreach (var Id in entity.Roles)
- 15 {
- 16 this.roleUserMappingRepository.Add(new RoleUserMapping()
- 17 {
- 18 Id = Guid.NewGuid(),
- 19 RoleId = Id,
- 20 LoginUserId = entity.Id,
- 21 CreateTime = DateTime.Now
- 22 });
- 23 }
- 24
- 25 this.unitOfWork.Commit();
- 26
- 27 return true;
- 28 }
- 29 catch (Exception ex)
- 30 {
- 31 Logger.Error(ex.Message, ex);
- 32 return false;
- 33 }
- 34 }
大部分人应该都是像上边一样处理异常,这本身没有问题
但是仔细想想,还是有点问题的,我们来看看
1、每个方法都要写 try{}catch{},到最后整个类库都是 try catch,这很丑。。,颜值太低了,就提不起多少兴趣了。
2、处理异常这本身就不应该属于业务逻辑的一部分,得把他弄走,因为他污染了业务逻辑
3、只记录了异常堆栈,输入参数没有,如果要记录参数值,还得写一堆日志代码,那就更丑了。
3.2 有困难,拦截器来帮忙;对于这种现象,于是一些聪明的开发者就搞出了 AOP 编程
AOP 是基于特性(Attribute)的,不过自己搞的话貌似还挺复杂的,简单的还行,复杂的我也不会
于是我就盯上了 PostSharp,版本 1.5 以上是收费的,这点要注意。我们先来搞一个简单的异常拦截器
- 1 [Serializable]
- 2 [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
- 3 public class ExceptionAttribute : PostSharp.Aspects.OnExceptionAspect
- 4 {
- 5 public override void OnException(MethodExecutionArgs args)
- 6 {
- 7 StringBuilder sb = new StringBuilder();
- 8 sb.AppendLine(args.Exception.Message);
- 9 sb.AppendFormat("位置:{0}.{1}", args.Method.ReflectedType, args.Method).AppendLine();
- 10
- 11 sb.AppendLine("参数:");
- 12 foreach(var item in args.Arguments)
- 13 {
- 14 sb.AppendFormat(item.ToString()).AppendLine();
- 15 }
- 16
- 17 Logger.Error(sb.ToString(), args.Exception);
- 18
- 19 args.FlowBehavior = FlowBehavior.ThrowException;
- 20 }
- 21 }
标红的得注意了,必须要写清楚的,不然没有效果
还有一个小坑就是 PostSharp 不支持实体参数的,传普通类型的 string、int 之类的在 OnException 中是可以拿到值的
如果是实体,比如这种
- 1 public class LoginUserCURequest 2 {
- 3 /// <summary>Id</summary>
- 4 public Guid Id {
- get;
- set;
- }
- 5 6 /// <summary>登录账户名</summary>
- 7 public string LoginName {
- get;
- set;
- }
- 8 9 /// <summary>登录密码</summary>
- 10 public string Password {
- get;
- set;
- }
- 11 12 /// <summary>是否有效</summary>
- 13 public short ? IsEnabled {
- get;
- set;
- }
- 14 15 /// <summary>所属角色</summary>
- 16 public IEnumerable Roles {
- get;
- set;
- }
- 17
- }
这种是取不到值的(橙色部分),因为 args.Arguments 返回的是 object 数组,取不到实体属性
这是很不好的一点,不过可以克服的,我的方法就是重写 ToString(),如下
1 public class LoginUserCURequest 2 { 3 /// <summary>Id</summary> 4 public Guid Id { get; set; } 5 6 /// <summary>登录账户名</summary> 7 public string LoginName { get; set; } 8 9 /// <summary>登录密码</summary> 10 public string Password { get; set; } 11 12 /// <summary>是否有效</summary> 13 public short ? IsEnabled { get; set; } 14 15 /// <summary>所属角色</summary> 16 public IEnumerable Roles { get; set; } 17 18 public override string ToString() 19 { 20 return string.Format("Id:{0},LoginName:{1},Password:{2},IsEnabled:{3}", 21 Id.ToString(), LoginName, Password, IsEnabled.ToString()); 22 } 23 }
这样就没问题了,还能按照自己的格式输出;
3.3 使用异常拦截器
拦截器我们已经搞好了,直接写在方法或类名上边就可以,如下图
然后我们再 Add 方法里边触发一个异常
我们测试一下,还是之前的 LoginUserApplicationTest.cs,这里再贴一次
1 [TestMethod] 2 public void Add() 3 { 4 var list = new List(); 5 list.Add(Guid.NewGuid()); 6 list.Add(Guid.NewGuid()); 7 8 var flag = this.loginUserApplication.Add(new LoginUserCURequest() 9 { 10 Id = Guid.NewGuid(), 11 LoginName = "lanxiaoke-" + Guid.NewGuid().ToString(), 12 Password = "123456", 13 IsEnabled = 1, 14 Roles = list 15 }); 16 17 Assert.AreEqual(true, flag); 18 }
测试未通过
看看数据库有没有记录到异常
看到没?有了这些日志信息,什么问题都无法遁形。。
来源: http://www.cnblogs.com/lanxiaoke/p/6574210.html