7. 友好异常处理
7.1 什么是异常
异常一般是指运行期(此处特指 Exception 类)会发生的导致程序意外中止的问题,是一种对问题的描述后的封装对象。
在过去开发中,通常异常由系统运行时出错抛出,但现在的开发过程中,我们应在程序开发中合理的抛出异常,比如更新一条不存在的实体,或查询一个不存在的数据等等。
7.2 处理异常方式
- 不处理,直接中断程序执行(不推荐)
- 通过
try catch finally处理(不推荐) - 全局统一处理,并记录异常信息(推荐)
- 异常注解方式处理,支持本地化 (推荐)
7.3 什么是友好异常处理
7.3.1 非友好异常处理
在了解友好异常处理之前可以看看非友好异常处理:
- 对终端用户抛出
500状态码堆栈信息 - 大量的
try catch代码,污染正常业务逻辑 - 没有规范化的异常状态码和异常消息管理
- 没有异常日志收集记录
- 不支持异常消息本地化处理
- 不支持异常策略,失败后程序立即终止
- 不支持分布式事务 CAP
- 不支持异常传播
- 返回的异常格式杂乱
7.3.2 友好异常处理
- 对终端用户提示友好
- 对后端开发人员提供详细的异常堆栈
- 不干扰正常业务逻辑代码,如 没有
try catch代码 - 支持异常状态码多方设置
- 支持异常消息本地化
- 异常信息统一配置管理
- 支持异常策略,如重试
- 支持异常日志收集记录
- 支持 CAP 分布式事务关联
- 支持内部异常外部传播
- 支持返回统一的异常格式数据
7.4 友好异常处理使用示例
Fur 框架提供了非常灵活的友好异常处理方式。
备注
.AddFriendlyException() 默认已经继承在 AddInject() 中了,无需再次注册。也就是 7.4.1 章节可不配置。
7.4.1 注册友好异常服务
特别注意
.AddFriendlyException() 需在 services.AddControllers() 之后注册。
7.4.2 两个例子
简单抛个异常
如下图所示:

抛出特定类型异常
如下图所示:

7.5 关于 Oops.Oh
通过上面的例子可以看出,Oops.Oh(errorMessage) 可以结合 throw 抛出异常。对于熟悉C#的人员来说,throw 后面只能 Exception 实例。Oops.Oh(...) 方法返回正是 Exception 实例。
7.6.1 为什么起这个名字?
这个名字来源于一个英语句子:Oh, Oops!,意思是 噢(哎),出错了!,所以就有了 Oops.Oh。
7.6.2 Oops.Oh 重载方法
7.6 最佳实践 🤗
在 Fur 框架中,提供了非常灵活且规范化的友好异常处理方式,通过这个方式可以方便管理异常状态码、异常信息及异常本地化。
7.6.1 创建异常信息类型
实现自定义异常信息类型必须遵循以下配置:
- 类型必须是公开且时
Enum枚举类型 - 枚举类型必须贴有
[ErrorCodeType]特性 - 枚举中每一项必须贴有
[ErrorCodeItemMetadata]特性
important
Fur 框架提供了 [ErrorCodeType] 特性和 IErrorCodeTypeProvider 提供器接口来提供异常信息扫描,这里用的是 [ErrorCodeType] 特性类。
7.6.2 关于 [ErrorCodeItemMetadata]
Fur 框架提供了[ErrorCodeItemMetadata] 特性用来标识枚举字段异常元数据,该特性支持传入 消息内容 和 格式化参数。最终会使用 String.Format(消息内容,格式化参数) 进行格式化。
如果消息内容中包含格式化占位符但未指定格式化参数,那么会查找异常所在方法是否贴有 [IfException] 特性且含有格式化参数,接着就会查找 Oops.Oh 中指定的 格式化参数。
7.6.3 静态异常类使用
如下图所示:

7.6.4 更多例子
7.7 多个异常信息类型
特别注意
多个异常静态类中也必须保证常量值唯一性,不可重复。
7.8 IErrorCodeTypeProvider 提供器
在 Fur 框架中,还提供了 IErrorCodeTypeProvider 异常消息提供器接口,方便在不能贴 [ErrorCodeType] 特性情况下使用:
启用 IErrorCodeTypeProvider 提供器:
小知识
只有使用 IErrorCodeTypeProvider 方式才需使用泛型方式注册。通过上面的方式注册可以同时支持 IErrorCodeTypeProvider 和 [ErrorCodeType] 方式。
7.9 appsetting.json 中配置
Fur 框架还提供了非常灵活的配置文件配置异常,通过这种方式可以实现异常信息后期配置,也就是无需在开发阶段预先定义。
Definitions 类型为二维数组,二维数组中的每一个数组第一个参数为 ErrorCode 也就是错误码,第二个参数为 ErrorMessage 消息内容,剩余参数作为 ErrorMessage 的格式化参数。
使用示例
小知识
[ErrorCodeType] 和 IErrorCodeTypeProvider 和 appsettings.json 可以同时使用。
7.10 [IfException] 使用
Fur 框架提供了 [IfException] 特性可以覆盖默认消息配置。也就是覆盖 异常消息类型 和 appsettings.json 中的配置。
特别注意
[IfException] 只能贴在方法上,支持多个,而且该方法所在的类类型必须是 ControllerBase 子类 或 实现 IDynamicApiController 接口。
7.10.1 使用示例
- 异常消息类定义
- 覆盖默认配置
如下图所示:

7.10.2 更多例子
格式化流程
如果消息内容中包含格式化占位符但未指定格式化参数,那么会查找异常所在方法是否贴有 [IfException] 特性且含有格式化参数,接着就会查找 Oops.Oh 中指定的 格式化参数。
7.11 异常消息优先级
[ErrorCodeItemMetadata] -> appsettings.json -> [IfException]。(低 -> 高)
[IfException]会覆盖appsettings.json定义的状态码消息。appsettings.json会覆盖[ErrorCodeItemMetadata]定义的消息。
7.12 多语言支持
文档整理中...
7.13 异常模型提供器
Fur 框架提供 规范化结果 功能,可以通过实现 IUnifyResultProvider 提供器实现异常返回值定制,如:RESTfulResultProvider:
之后在 Startup.cs 中注册即可:
7.14 全局异常处理提供器
通常我们需要在异常捕获的时候写日志,这时候就需要使用到 IGlobalExceptionHandler 异常定义处理程序,如:
7.15 反馈与建议
与我们交流
给 Fur 提 Issue。