本系列目录:Abp介绍和经验分享-目录

前言

ABP中有个异常UserFriendlyException经常被使用,但是它所在的命名空间是Abp.UI,总觉得和展现层联系过于紧密,在AppService中用也就罢了,在领域层中用它总觉得有点不合适。

那么怎么定义业务异常?既要用起来舒服又能体现业务意义?

几点目标

  1. 无需每个业务领域都定义各自的异常类,但使用时要有一定的可读性,能区分不同业务;
  2. 要有错误码;
  3. 每个错误码对应的提示语不能硬编码,最好能使用已有的本地化语言机制;
  4. 要有日志级别,不同的业务异常应该可以设置不同的日志级别,无特殊要求,应默认Warn级别;

以下代码从本系列QuickStartA中的Personball.Demo解决方案开始

先从错误码入手

从上面的要求来看,错误码定义成枚举最合适不过:

  1. 每个枚举值,既可以表达成字符串,又可以表达成数字;
  2. 枚举值字符串,在使用时可读性好,也可以直接作为本地化语言键值对中的键名;
  3. 枚举值数字,直接对应AjaxResponse中Error部分的Code(int类型);

我们打开Personball.Demo的解决方案,在Personball.Demo.Core项目的根目录中添加枚举定义ErrorCode

public enum ErrorCode : int
{
//用region提前分配错误编码给各个业务领域
//防止团队成员开发时发生冲突
#region 库存相关 100-110
/// <summary>
/// 库存不足
/// </summary>
InventoryNotEnough = 100,
#endregion #region 购物车相关 200-299
/// <summary>
/// 购物车项不存在
/// </summary>
ShoppingCartItemNotExists = 200, /// <summary>
/// 商品已下架
/// </summary>
ShoppingCartItemIsShelve = 201, /// <summary>
/// 数量超出范围
/// </summary>
ShoppingCartItemQtyMoreThanRange = 202,
#endregion #region 订单中心 600-649
/// <summary>
/// 订单项重复
/// </summary>
OrderItemDuplicate = 601, /// <summary>
/// 订单优惠(折扣)项重复
/// </summary>
OrderDiscountDuplicate = 602,
#endregion
}

针对错误码的定义,强烈建议使用英文单词描述该错误发生的原因,而不是仅仅表达一个结果或者现象。

比如,【订单项重复】和【订单优惠项重复】比一个笼统的【订单创建失败】要好。

英文单词可以尽可能长,如果觉得看不清楚,也可以采用下划线分隔。

如果英文能力不足,就尽量在注释上描述清楚,注释在最后可以统一录入语言文件作为本地化提示语。

给几个例子:

/// <summary>
/// 团队申请人或挑选的成员已创建过团队,不能重复创建
/// </summary>
PspOrganizationLeaderOrMembersAlreadyHasOrgs = 412, /// <summary>
/// 非消费者等级的账号只能属于自己的团队!
/// </summary>
PspAccountCannotChangeOrganizationWhenLevelGreaterThanLevelOne = 414, /// <summary>
/// 创建新团队时,只能包含Leader直接推荐的同级成员
/// </summary>
PspOrganizationCreateNewCanOnlyIncludeLeaderChildrenWhichInSameLevel = 417,

定义业务异常

现在我们已经有了ErrorCode,而且ErrorCode很好的表达了业务到底发生了什么,接着我们看看异常怎么定义。

我们一开始定的几个目标,其实大部分已经被枚举化的ErrorCode结合本地化语言机制满足了,所以我们的业务异常肯定包含这个ErrorCode枚举:

public class DemoBusinessException : AbpException, IHasErrorCode, IHasLogSeverity
{
public DemoBusinessException(ErrorCode errorCode)
: base(errorCode.ToString())
{
Code = (int)errorCode;
} public DemoBusinessException(ErrorCode errorCode, string message)
: base(message)
{
Code = (int)errorCode;
} public DemoBusinessException(ErrorCode errorCode, Exception innerException)
: base(errorCode.ToString(), innerException)
{
Code = (int)errorCode;
} public DemoBusinessException(
ErrorCode errorCode, string message, Exception innerException)
: base(message, innerException)
{
Code = (int)errorCode;
} public int Code { get; set; }
public LogSeverity Severity { get; set; } = LogSeverity.Warn;
}
  1. 继承AbpException,其中base(errorCode.ToString())构造方法中的参数对应的是AbpExceptionstring message,我们用枚举值ToString是为了之后方便处理提示语本地化。当然从重载提供的多个构造方法来看,也是支持覆盖这套机制的,可以直接写message。
  2. 继承IHasErrorCode,是为了告诉异常Handle代码,这个异常携带了错误码。
  3. 继承IHasLogSeverity,是为了告诉异常Handle代码,这个异常应该以哪个日志级别进行记录。

这个业务异常的使用范例:

//典型用法
throw new DemoBusinessException(ErrorCode.InventoryNotEnough);
//直接硬编码提示语
throw new DemoBusinessException(ErrorCode.InventoryNotEnough,
$"{item.Name}库存不足!");
//设置日志等级
throw new DemoBusinessException(ErrorCode.InventoryNotEnough)
{
Severity = Abp.Logging.LogSeverity.Error
};

最后,异常Handle代码

刚才提到的异常Handle代码,其实Abp提供了很好的扩展,就是IExceptionToErrorInfoConverter,先看看如何注册自定义实现:

Personball.Demo.Web项目,App_Start目录下的DemoWebModule

public override void PostInitialize()
{
var errorInfoBuilder = IocManager.Resolve<IErrorInfoBuilder>();
errorInfoBuilder.AddExceptionConverter(
IocManager.Resolve<CustomExceptionErrorInfoConverter>());
}

其中CustomExceptionErrorInfoConverter,就是我们要自定义的类:

public class CustomExceptionErrorInfoConverter
: IExceptionToErrorInfoConverter, ITransientDependency
{
private readonly ILocalizationManager _localizationManager; public IExceptionToErrorInfoConverter Next { set; private get; } public CustomExceptionErrorInfoConverter(ILocalizationManager localizationManager)
{
_localizationManager = localizationManager;
} public ErrorInfo Convert(Exception exception)
{
while (exception is AggregateException && exception.InnerException != null)
{
exception = exception.InnerException;
} if (exception is DemoBusinessException)
{
var ex = exception as DemoBusinessException;
return new ErrorInfo(ex.Code, L(ex.Message));
} if (exception is EntityNotFoundException)
{
return new ErrorInfo((int)ErrorCode.ItemNotExists, L(ErrorCode.ItemNotExists.ToString()));
} if (exception is ArgumentException)
{
var argEx = exception as ArgumentException;
var argMsg = exception.Message ?? L(ErrorCode.RequestParametersError.ToString());
return new ErrorInfo((int)ErrorCode.RequestParametersError, argMsg, $"ParamName:{argEx.ParamName}");
} return Next.Convert(exception);
} private string L(string name)
{
try
{
return _localizationManager.GetString(DemoConsts.LocalizationSourceName, name);
}
catch (Exception)
{
return name;
}
}
}

可以启动看看效果了

我们在登录处理逻辑上抛个业务异常看看效果。

找到Personball.Demo.Web下的AccountController,添加一行如下:

[HttpPost]
[DisableAuditing]
public async Task<JsonResult> Login(
LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
{
//在此抛出业务异常
throw new DemoBusinessException(ErrorCode.InventoryNotEnough); CheckModelState(); //...略
}

运行后,点击登陆,如图:

看响应中的错误码,是100,提示语是[Inventory not enough],因为忘了配置语言文件,所以这里没本地化。

打开Personball.Demo.Core下的目录Localization\Source,编辑Demo-zh-CN.xml,追加一行:

<text name="InventoryNotEnough" value="库存不足"/>

再运行之前的登陆看看,是不是变成中文了?

本文源码下载

Personball.Demo.ErrorCode.7z

[2017-08-28]Abp系列——业务异常与错误码设计及提示语的本地化的更多相关文章

  1. 使用whistle模拟cgi接口异常:错误码、502、慢网速、超时

    绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中.如果没有做好异常的兼容和兜底处理,会极大的影响用户体验,严重的还会带来安 ...

  2. .NET中异常与错误码优劣势对比

    .NET之所以选择异常,而不是返回错误码来报告异常,是由于前者有以下几个优势: 1.异常与oop语言的结合性更好.oop语言经常需要对成员签名强加限制,比如c#中的构造函数.操作符重载和属性,开发者对 ...

  3. C++异常 返回错误码

    一种比异常终止更灵活的方法是,使用函数的返回值来指出问题.例如,ostream类的get(void)成员ASCII码,但到达文件尾时,将返回特殊值EOF.对hmean()来说,这种方法不管用.任何树脂 ...

  4. 2017.4.28 KVM 内存虚拟化及其实现

    概述 KVM(Kernel Virtual Machine) , 作为开源的内核虚拟机,越来越受到 IBM,Redhat,HP,Intel 等各大公司的大力支持,基于 KVM 的开源虚拟化生态系统也日 ...

  5. 基于DDD的现代ASP.NET开发框架--ABP系列之3、ABP分层架构

    基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:ht ...

  6. 基于DDD的现代ASP.NET开发框架--ABP系列之2、ABP入门教程

    基于DDD的现代ASP.NET开发框架--ABP系列之2.ABP入门教程 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...

  7. 点这里进入ABP系列文章总目录

    基于DDD的现代ASP.NET开发框架--ABP系列之1.ABP总体介绍 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...

  8. 基于DDD的现代ASP.NET开发框架--ABP系列之1、ABP总体介绍

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之1.ABP总体介绍 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...

  9. 浅入 ABP 系列(4):事件总线

    浅入 ABP 系列(4):事件总线 版权护体作者:痴者工良,微信公众号转载文章需要 <NCC开源社区>同意. 目录 浅入 ABP 系列(4):事件总线 事件总线 关于事件总线 为什么需要这 ...

随机推荐

  1. iOS开发实战-时光记账Demo 本地数据库版

    现在记账APP也是用途比较广泛 自己写了个简单的demo 欢迎指正 效果 分析 1.思维推导 首先简单的做了下思维推导 2.文件结构 大致框架想好后就可以着手开始准备了 数据库管理:coreData ...

  2. HDOJ-2009 求数列的和

    Problem Description 数列的定义如下:数列的第一项为n,以后各项为前一项的平方根,求数列的前m项的和.   Input 输入数据有多组,每组占一行,由两个整数n(n<10000 ...

  3. [js] webgl 初探 - 绘制三角形

    摘要: 1. webgl 概念挺多的, 顶点着色器.片段着色器, 坐标 2. 绘制前期准备工作好多 目前看的比较好的教材: https://developer.mozilla.org/zh-CN/do ...

  4. gdb命令中查看地址之x命令

    可以使用examine命令(简写是x)来查看内存地址中的值.x命令的语法如下所示: x/<n/f/u> <addr> n.f.u是可选的参数. n是一个正整数,表示需要显示的内 ...

  5. 蓝桥杯比赛java 练习《立方变自身》

    立方变自身 观察下面的现象,某个数字的立方,按位累加仍然等于自身.1^3 = 1 8^3  = 512    5+1+2=817^3 = 4913   4+9+1+3=17... 请你计算包括1,8, ...

  6. JavaSE(七)之内部类

    上一篇我们学习了接口还有访问控制,在以后的工作中接口是我们经常要碰到的,所以一定要多去回顾.接下来介绍一下内部类.很多时候我们创建类的对象的时候并不需要使用很多次,每次只使用一次 这个时候我们就可以使 ...

  7. [补档]vijos1883 月光的魔法

    vijos1883 月光的魔法 题目 传送门:https://www.vijos.org/p/1883 背景 影几欺哄了众生了 天以外-- 月儿何曾圆缺   描述 有些东西就如同月光的魔法一般. Lu ...

  8. cve-2017-0199&metasploit复现过程

    CVE-2017-0199 WORD/RTF嵌入OLE调用远程文件执行的一个漏洞.不需要用户交互.打开文档即中招 首先更新msf到最新,据说最新版简化了利用过程,不需要开启hta这一步.但没测成功 还 ...

  9. [转]为什么大型网站前端使用 PHP 后台逻辑用 Java?

    最近纠结了一下,如果开发一个大型的网站,我到底应该使用php还是jsp,后台到底使用php还是用java,我的选择要么是php要么是java,因为我喜欢linux.unix,当然window平台也必须 ...

  10. webpack学习--创建一个webpack打包流程

    创建一个webpack打包流程 首先安装webpack插件 mkdir webpack-demo && cd webpack-demo npm init -y npm install ...