在上篇 《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:怎样处理程序中的异常(实战篇)?相关推荐

  1. dotNET:怎样处理程序中的异常(理论篇)?

    平时在软件开发的过程中,首先是要保证功能可以正常运行,满足业务需求,除此之外,还需要考虑代码在异常的时候怎么处理,让程序能够健壮地运行.正确合理地处理异常可以减少程序的 Bug.保证代码质量,当然也不 ...

  2. python中的异常

    1.异常的概念 程序在运行时,如果Python 解释器遇到到一个错误,会停止程序的执行,并且提示一些错误信息,这就是异常 程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出(raise)异常 ...

  3. ASP.NET Google Maps Javascript API V3 实战基础篇一获取和设置事件处理程序中的属性...

    ASP.NET Google Maps Javascript API V3 实战基础篇一获取和设置事件处理程序中的属性 <%@ Page Language="C#" Auto ...

  4. iOS内存管控实战(中)-分析工具篇

    因文章单篇过长,按照 原理.分析工具 和 实战 拆分成上.中.下三部分,点击阅读. iOS内存管控实战(上)-原理篇 iOS内存管控实战(中)-分析工具篇 iOS内存管控实战(下)-实战篇 二.内存分 ...

  5. 三、C++反作弊对抗实战 (实战篇 —— 3.如何获取游戏中角色人物角色的名称坐标、血量、武器信息(非CE扫描))

    提示:本章节将介绍如何获取CS1.6游戏中角色人物角色的名称.坐标.血量.武器信息(非CE扫描 前言 在上一章节中<三.C++反作弊对抗实战 (实战篇 -- 2.认识CS1.6常见的数据结构与流 ...

  6. 《Autosar从入门到精通-实战篇》总目录_培训教程持续更新中...

    目录 一.Autosar入门篇: 1.1 DBC专题(共9篇) 1.2 ARXML专题(共35篇) 1.2.1 CAN Matrix Arxml(共28篇) 1.2.2 ASWC Arxml(共7篇) ...

  7. MySQL的进阶实战篇

    关联文章: MySQL的初次见面礼基础实战篇 MySQL的进阶实战篇 本篇上一篇博文MySQL的初次见面礼基础实战篇的延续,是mysql的进阶内容的记录,本篇主要知识点如下: 进阶实战篇 进阶实战篇 ...

  8. Systemd 入门教程:实战篇

    Systemd 入门教程:实战篇 原文出处: 阮一峰(@ruanyf)   http://blog.jobbole.com/98671/?utm_source=blog.jobbole.com& ...

  9. PHP ERROR_php中的异常和错误浅析

    本文主要介绍了php中的异常和错误,分享给大家供大家参考学习,下面来一起看看详细的介绍: 一.异常与错误 异常是指程序运行中不符合预期情况以及与正常流程不同的状况.错误则属于自身问题,是一种非法语法或 ...

最新文章

  1. **Git本地仓库图解
  2. 深入理解Golang包导入
  3. 给定一个32位有符号整数,将整数中的数字进行翻转
  4. Loadrunner 入门连载教程
  5. 计算机基础知识-操作系统
  6. 彻彻底底了解回调函数
  7. ES6 Symbol 数据类型
  8. Linux Shell文本处理工具集锦
  9. spring_boot的logback-spring.xml配置为什么 %d{yyyy-MM-dd} 不起作用
  10. 安卓桌面壁纸_安卓视频桌面哪个好用 让手机桌面更炫酷
  11. iOS中控制器的实践和学习(2)-认识XCode4模版(A1,A3,B2简易图)
  12. 实现网页布局的自适应 利用@media screen
  13. postman 接口测试用例设计
  14. Flash Builder 4 正式版破解注册方法(flex4)
  15. linux怎么进tmp目录,关于linux下tmp文件夹
  16. 货拉拉Android稳定性治理
  17. 企业研发人员配备比例_如何理解高新技术企业认定对研发人员比例的要求
  18. 使用Processing实现井字棋
  19. 计算机信息安全-病毒,信息安全-计算机病毒.doc
  20. 刷题找工作《买卖股票问题》一文通解

热门文章

  1. EntityFramework6.X 之 Fulent
  2. 网络相关配置,SSH服务,bash, 元字符
  3. 机器学习 vs. 深度学习
  4. vi 编辑器跳转到指定行数
  5. FFT算法的完整DSP实现(转)
  6. poj 1060 Modular multiplication of polynomials
  7. [WP] 关于页面切换动画 记录
  8. 感受机房管理化繁为简-新款KVM使用心得
  9. SON Web Token设计单点登录系统
  10. hdu 1848(Fibonacci again and again)(SG博弈)