包装Action返回结果
许多开发人员喜欢给Action的返回结果再做一层包装,例如:
{
"code": 1,
"message": "xxxxxxxx",
"data": {这里是原本Action的返回结果}
}
效果演示
Nebula对这种需求提供了内置支持,不用考虑这个包装对象的存在,Action只返回该返回的数据就可以了,例如:
[Route("return/namevalue")]
public NameValue ReturnObject2()
{
return new NameValue("abc", "12345");
}
服务端的返回结果:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"code": 1,
"message": "",
"detailMessage": "",
"data": {
"Name": "abc",
"Value": "12345"
}
}
如果遇到异常
[Route("return/error")]
public int ReturnError()
{
if( DateTime.Now.Year > 2000 )
throw new MessageException("一个测试消息异常") { StatusCode = 555 };
else
return 111;
}
服务端的返回结果:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"code": 555,
"message": "一个测试消息异常",
"detailMessage": "ex.ToString()",
"data": ""
}
如何实现?
1,开启一个开头(默认是关闭状态)
在ClownFish.App.config中添加:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Nebula_WrapDataResultFilter_Enable" value="1" />
</appSettings>
</configuration>
或者用环境变量也可以,例如:
docker run -d --name xxxxx \
-e Nebula_WrapDataResultFilter_Enable=1 \
2,对返回结果做打包:
先定义包装类型:
public record WrapData(int code, string message, string detailMessage, object data);
再重写 NebulaBehavior.WrapActionResult 方法。
public override object WrapActionResult(object resultOrException)
{
// Action可能会返回NULL
if( resultOrException == null) {
return new WrapData(1, "", "", "");
}
// Action可能返回了一个简单的:数字/字符串
if( resultOrException is string || resultOrException.GetType().IsPrimitive ) {
return new WrapData(1, "", "", resultOrException.ToString());
}
// Action中出现了异常
if( resultOrException is Exception ex ) {
return new WrapData(ex.GetErrorCode(),
ex.Message,
"ex.ToString()", // 实际使用时,不需要用双引号
"");
}
// Action的“正常”返回数据
return new WrapData(1, "", "", resultOrException);
}
3,调用API
GET http://localhost:8206/v20/api/WebSiteApp/wrapdata/return/namevalue HTTP/1.1
x-wrap-result: 1
注意上面的调用中,有一个 x-wrap-result 的请求头。
如果没有这个请求头,结果将不会做包装!
谈谈这种API的设计方式
"对Action返回结果再做包装",最初的设计者可能认为(后来人应该是无脑抄):
这种API对调用者会比较友好,因为可以不用考虑异常情况,调用代码会比较简单。
他想像中的调用代码:
var result = call_httpapi(...);
if( errorcode == 1){
// 调用成功
// 继续执行其它业务操作
}
else{
// 调用失败
// ################ 在这里处理异常 111111111111111
}
国内大厂的许多外部API也确实都采用了这种设计方式,但这种做法其实是 非常差劲的!
因为有些异常情况是不受业务代码控制的,例如:
- 网络不通
- 服务不可用
- 服务端出现不可控异常,例如:OOM
在这些场景下,前面所说的包装方式就无效了,最后调用代码还是必须要增加 try...catch 来捕获异常,例如:
try{
var result = call_httpapi(...);
if( errorcode == 1){
// 调用成功
// 继续执行其它业务操作
}
else{
// 调用失败
// ################ 在这里处理异常 111111111111111
}
}
catch(Exception ex){
// ################ 这里处理其它类型的异常 222222222222222
}
如果业务项目有全局异常处理机制,errorcode != 1 的场景反而会更麻烦,它需要主动抛出一个异常,来结束当前代码块!
综上所述,这种做法虽然在国内很流行,但是设计想法即是非常愚蠢的!
调用代码不仅得不到简化,反而会更复杂,除非调用方意识不到要做异常处理!