dotNET:怎样处理程序中的异常(实战篇)?
在上篇 《dotNET:怎样处理程序中的异常(理论篇)》 中讲了一些程序中出现异常怎样处理的理论知识,本文将以代码的方式来进行实践。
环境
dotNET Core:3.1
工具:Rider 2019.3.2
系统:macOS 10.15.4
创建项目
在 Rider 中创建示例项目 ExceptionDemo ,该项目为 dotNET Core 3.1 的 WebAPI 项目,为了演示方便,不同层级以目录的方式放在了一个项目中,创建好的项目目录结构如下:
Controllers
UserController:操作用户的控制器
CustomExceptions
UserNotFoundException:用户不存在的自定义异常类
Filters
CustomerExceptionAttribute:异常结果处理过滤器
ResultFilterAttribute:普通结果处理过滤器
Models
CustomExceptionResult:异常返回的处理类
CustomExceptionResultModel:异常内容的模型类
DataResult:普通结果的返回处理类
DataResultModel:普通结果的内容模型类
MessageResult:消息结果的返回处理类
MessageResultModel:消息结果的内容模型类
ResultModelBase:返回结果内容模型的基类
User:示例中用户的实体类
Repositories
IUserRepository:用户操作数据库的接口
UserRepository:用户操作数据库的实现类
Services
IUserService:用户业务层的接口
UserService:用户业务层的实现类
结果的返回
接口的返回可以归纳为三种情况:
正常的请求数据的返回
通过判断需要返回一些消息给前端进行提示
异常的返回
所以上面定义了 DataResult、MessageResult 和 CustomExceptionResult 相关类来进行这三种情况的封装。
这三个类都继承 ResultModelBase 类,ResultModelBase 类中只定义了 Code
public class ResultModelBase
{public int? Code { get; set; }
}
DataResultModel 类用属性 Data 来包装返回结果
public class DataResultModel:ResultModelBase
{public DataResultModel(object data,int? code = 200){Code = code;Data = data;}public object Data { get; set; }
}
MessageResultModel 类使用属性 Message 类返回消息文本
public class MessageResultModel:ResultModelBase
{public MessageResultModel(string massage,int? code = 200){Code = code;Message = massage;}public string Message { get; set; }
}
CustomExceptionResultModel 类中可以传入 Exception 类型和定义一些其他的相关属性
public class CustomExceptionResultModel:ResultModelBase
{public CustomExceptionResultModel(Exception exception,int? code = 500){Code = code;Reason = exception.InnerException != null ?exception.InnerException.Message :exception.Message;}public string Reason { get; set; }
}
DataResult、MessageResult 和 CustomExceptionResult 类都是继承自ObjectResult,将相对应的 Model 类包装后通过构造函数赋值给 ObjectResult 的 Value 属性,用于最后的结果返回。
public class DataResult: ObjectResult
{public DataResult(object data , int? code=200 ): base(new DataResultModel(data,code)){StatusCode = 200;}
}
public class MessageResult:ObjectResult
{public MessageResult(string message, int? code=200 ): base(new MessageResultModel(message,code)){StatusCode = 200;}
}
public class CustomExceptionResult:ObjectResult
{public CustomExceptionResult(Exception exception,HttpStatusCode statusCode, int? code=500 ): base(new CustomExceptionResultModel(exception,code)){StatusCode = (int)statusCode;}
}
使用两个过滤器对返回结果进行处理
public class CustomerExceptionAttribute: IExceptionFilter
{public void OnException(ExceptionContext context){HttpStatusCode status = HttpStatusCode.InternalServerError;int code = (int) status;//处理各种异常if (context.Exception is UserNotFoundException){code = 500001;}context.Result = new CustomExceptionResult(context.Exception,status ,code);context.ExceptionHandled = true;}
}public class ResultFilterAttribute:ActionFilterAttribute
{public override void OnResultExecuting(ResultExecutingContext context){var objectResult = context.Result as ObjectResult;if (objectResult?.Value == null){context.Result=new NotFoundObjectResult(new MessageResult("未找到资源"));}if (context.Result is MessageResult){context.Result = new MessageResult(objectResult.Value.ToString());}else if (context.Result is OkObjectResult || context.Result is ObjectResult){context.Result = new DataResult(objectResult.Value);}}
}
用户添加接口
在 UserRepository 中添加 AddUser 方法
public User AddUser(User user)
{int id=_users.OrderByDescending(x => x.Id).First().Id + 1;user.Id = id;_users.Add(user);return user;
}
示例中没有实际操作数据库,_users 是一个 List对象,当 _users 为 Null 或内容为空时,_users.OrderByDescending(x => x.Id).First()
的执行就会报错,空对象的问题在实际程序中无处不在,修改后的代码如下:
public User AddUser(User user)
{int id = 1;if (_users.Any()){id=_users.OrderByDescending(x => x.Id).First().Id + 1;}user.Id = id;_users.Add(user);return user;
}
在 Controller 层的 AddUser 方法也需要对入参实体进行检查
[HttpPost]
public User AddUser(User user)
{return _userService.AddUser(user);
}public class User
{public int Id { get; set; }[Required(ErrorMessage = "用户名不能为空")]public string Name { get; set; }[Required(ErrorMessage = "用户编码不能为空")]public string Code { get; set; }
}
实际情况下接口层的入参实体和底层的数据实体需要分开,然后使用 AutoMapper 之类的映射工具进行转换,本示例中使用了同一个 User 。
使用 Postman 进行调用,当 Name 或 Code 为空时,结果如下:
默认的返回结果格式和上面定义的统一的格式有些区别,大家可以思考下,怎样使用过滤器的方式将参数验证的返回信息进行统一输出。
根据 Id 获取用户的名称
在 UserRepository 中有根据 Id 获取 User 对象的方法
public User GetUserById(int id)
{return _users.Find(x => x.Id == id);
}
在 UserService 中添加 GetUserName 方法获取名称
public string GetUserName(int id)
{User user=_userRepository.GetUserById(id);if (user == null){throw new UserNotFoundException($"用户id:{id} 在数据库不存在" );}return user.Name;
}
当通过 id 找不到 User 对象时,可以抛出 UserNotFoundException 异常,如果只是对 user 对象进行 Null 判断然后返回一个空字符,就弄不清楚是 user 对象不存在还是用户名为空。
获取用户全名
下面用一个获取用户全名(包含部门)的业务来模拟异常的重新包装,部门操作的相关类就不在赘述了,可以在文章最下方的链接中查看源码。
UserController 中添加了接口方法
[HttpGet("{id}")]
public string GetFullName(int id)
{return _userService.GetFullName(id);
}
UserService 中添加 GetFullName 方法
public string GetFullName(int id)
{try{User user = GetUserById(id);string deptName = _deptService.GetDeptName(user.ParentId);//处理其他逻辑return $"{user.Name}[{deptName}]";}catch (Exception e){throw new UserFullNameGenException($"用户 Id 为 {id} 的 FullName 生产失败",e);}
}
GetUserById 方法和 _deptService.GetDeptName 方法中都可能抛异常,在上次可以捕获异常然后抛出符合当前业务的 UserFullNameGenException 异常;
捕获的异常 e 作为 UserFullNameGenException 异常的 InnerException 传入,这样如果层级比较多,通过 InnerException 就可以追溯到最底层的原因。
当输入参数为用户不存在的时候调用结果如下:
当输入参数为用户的部门不存在时调用结果如下:
通过二次捕获提示的错误信息是跟当前业务有关的,可以更容易定位问题,更底一层的原因可以在 InnerException 中获取;
两次异常是不同原因造成的,但对于这个业务来说就是获取 FullName 失败,返回的错误码也是一致的 500100 ;
因为有了二次捕获,异常堆栈信息中只能定位到最上层捕获异常的地方,如果需要知道更底层的异常堆栈,可以将 InnerException 的堆栈信息进行合并。
最后
本文以一个简单的示例演示了代码中异常的处理,但重要的不是编码而是处理问题的思路。具体应该怎么做还是需要结合当前的上下文。希望本文对您有所帮助。
示例源码:https://github.com/oec2003/DotNetCoreThreeAPIDemo/tree/master/ExceptionDemo
dotNET:怎样处理程序中的异常(实战篇)?相关推荐
- dotNET:怎样处理程序中的异常(理论篇)?
平时在软件开发的过程中,首先是要保证功能可以正常运行,满足业务需求,除此之外,还需要考虑代码在异常的时候怎么处理,让程序能够健壮地运行.正确合理地处理异常可以减少程序的 Bug.保证代码质量,当然也不 ...
- python中的异常
1.异常的概念 程序在运行时,如果Python 解释器遇到到一个错误,会停止程序的执行,并且提示一些错误信息,这就是异常 程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出(raise)异常 ...
- ASP.NET Google Maps Javascript API V3 实战基础篇一获取和设置事件处理程序中的属性...
ASP.NET Google Maps Javascript API V3 实战基础篇一获取和设置事件处理程序中的属性 <%@ Page Language="C#" Auto ...
- iOS内存管控实战(中)-分析工具篇
因文章单篇过长,按照 原理.分析工具 和 实战 拆分成上.中.下三部分,点击阅读. iOS内存管控实战(上)-原理篇 iOS内存管控实战(中)-分析工具篇 iOS内存管控实战(下)-实战篇 二.内存分 ...
- 三、C++反作弊对抗实战 (实战篇 —— 3.如何获取游戏中角色人物角色的名称坐标、血量、武器信息(非CE扫描))
提示:本章节将介绍如何获取CS1.6游戏中角色人物角色的名称.坐标.血量.武器信息(非CE扫描 前言 在上一章节中<三.C++反作弊对抗实战 (实战篇 -- 2.认识CS1.6常见的数据结构与流 ...
- 《Autosar从入门到精通-实战篇》总目录_培训教程持续更新中...
目录 一.Autosar入门篇: 1.1 DBC专题(共9篇) 1.2 ARXML专题(共35篇) 1.2.1 CAN Matrix Arxml(共28篇) 1.2.2 ASWC Arxml(共7篇) ...
- MySQL的进阶实战篇
关联文章: MySQL的初次见面礼基础实战篇 MySQL的进阶实战篇 本篇上一篇博文MySQL的初次见面礼基础实战篇的延续,是mysql的进阶内容的记录,本篇主要知识点如下: 进阶实战篇 进阶实战篇 ...
- Systemd 入门教程:实战篇
Systemd 入门教程:实战篇 原文出处: 阮一峰(@ruanyf) http://blog.jobbole.com/98671/?utm_source=blog.jobbole.com& ...
- PHP ERROR_php中的异常和错误浅析
本文主要介绍了php中的异常和错误,分享给大家供大家参考学习,下面来一起看看详细的介绍: 一.异常与错误 异常是指程序运行中不符合预期情况以及与正常流程不同的状况.错误则属于自身问题,是一种非法语法或 ...
最新文章
- **Git本地仓库图解
- 深入理解Golang包导入
- 给定一个32位有符号整数,将整数中的数字进行翻转
- Loadrunner 入门连载教程
- 计算机基础知识-操作系统
- 彻彻底底了解回调函数
- ES6 Symbol 数据类型
- Linux Shell文本处理工具集锦
- spring_boot的logback-spring.xml配置为什么 %d{yyyy-MM-dd} 不起作用
- 安卓桌面壁纸_安卓视频桌面哪个好用 让手机桌面更炫酷
- iOS中控制器的实践和学习(2)-认识XCode4模版(A1,A3,B2简易图)
- 实现网页布局的自适应 利用@media screen
- postman 接口测试用例设计
- Flash Builder 4 正式版破解注册方法(flex4)
- linux怎么进tmp目录,关于linux下tmp文件夹
- 货拉拉Android稳定性治理
- 企业研发人员配备比例_如何理解高新技术企业认定对研发人员比例的要求
- 使用Processing实现井字棋
- 计算机信息安全-病毒,信息安全-计算机病毒.doc
- 刷题找工作《买卖股票问题》一文通解