6.6 ABP表现层 - AJAX API
6.6.2.1 AJAX操作问题
现代的应用经常会使用AJAX,尤其是单页应用,几乎是和服务器通信的唯一手段,执行AJAX通常会有以下步骤:
基本上:为了执行一个AJAX调用,首先你要在客户端提供一个可供请求的URL,选取提交数据和一个方法(GET,POST,PUT,DELETE)。
等待调用完成后,处理返回结果。当执行AJAX调用服务器端的时候,可能会有错误(一般是网络错误)。当然也有可能是服务器端产生了一些错误,对于这些错误会,服务器会返回一个失败的响应并且附上错误消息给客户端。
客户端代码应该处理这些错误,并且可以选择通知用户(可以显示一个错误对话框)。如果没有错误且服务器端返回了数据,客户端必须处理它。还有你应该限制页面的某个区域(或者整个页面),并显示一个忙碌的指示直到AJAX操作完成。
服务器端在得到请求后执行服务器端代码,捕获异常并返回一个有效的响应给客户端。在错误情况下,可以选择发送错误消息给客户端。如果是验证错误,服务器端可以添加验证错误的验证信息。在成功情况下,可以发送返回值给客户端。
6.6.2.2 ABP的方式
由于使用 abp.ajax 函数对AJAX调用进行了封装, 所以ABP能自动化这些步骤。下面是一个AJAX调用示例:
var newPerson = {
name: 'Dougles Adams',
age: 42
};
abp.ajax({
url: '/People/SavePerson',
data: JSON.stringify(newPerson)
}).done(function(data) {
abp.notify.success('created new person with id = ' + data.personId);
});
abp.ajax得到 options 作为对象。你可以传递任何有效的jQuery的 $.ajax 函数中的参数。有一些默认参数:dataType 是 json,type是 POST,还有 contentType是 application/json(在发送数据到服务器端之前,我们需要调用 JSON.stringify 将脚本对象转换为JSON字符串)。通过对apb.ajax传递options可以覆盖默认值。
abp.ajax返回promise。因此,你可以写这些处理函数:done,fail,then等等。在这个例子中,我们对 PeopleController’s SavePerson action 发送了一个简单的AJAX请求。在 done 处理函数中,我们对新创建的person取得了它的主键id并且显示了创建成功的通知。让我们看看 MVC Controller:
public class PeopleController : AbpController
{
[HttpPost]
public JsonResult SavePerson(SavePersonModel person)
{
//TODO: 保存新创建的person到数据库并且返回person的id
return Json(new {PersonId = 42});
}
}
正如你猜测的 SavePersonModel 包含了Name和Age属性。SavePerson 被标记为 HttpPost 特性,因为abp.ajax默认方法是POST。通过返回了匿名对象简化了方法实现。
这个看上去很简单直白,但是ABP在背后做了很多重要的处理。让我们深入了解一下:
6.6.2.3 AJAX 返回消息
即使我们直接的返回了一个带有PersonId = 2 的对象,ABP也会使用 MvcAjaxResponse 对象来包装它。事实上AJAX响应返回的内容应该像下面一样:
{
"success": true,
"result": {
"personId": 42
},
"error": null,
"targetUrl": null,
"unAuthorizedRequest": false,
"__abp": true
}
在这里所有的属性都是驼峰命名的(因为这在JavaScript中是惯例),即使在服务端代码中是PascalCased的。下面解释一下所有的字段:
success:boolean类型的值(true或者false),用来表示操作的成功状态。如果是ture,abp.ajax会解析该promise并且调用 done 函数。如果是false(在方法被调用的时候,如果有个异常被抛出),它会调用 fail 函数并且使用 abp.message.error 函数显示 error 消息。
result:控制器的action的实际返回值。如果success是ture并且服务器发送了返回值那么它才是有效的。
error:如果success是false,这个字段是一个包含 message和details 字段的对象。
targetUrl:如果需要的话,这提供了一种可能性:服务器端发送一个URL到客户端,使客户端可以重定向到其它的URL。
unAuthorizedRequest:这提供了一种可能性:服务器端发送通知给客户端该操作未被授权,或者是未认证用户。如果该值是true,那么abp.ajax会 reloads 当前的页面。
__abp:通过ABP包装响应返回的特殊签名。你自己不需要用到它,但是abp.ajax会处理它。
这种格式的对象会被 abp.ajax 函数识别且处理。abp.ajax会得到控制器的实际返回值(一个带有personid属性的对象),如果没有错误的话,那么你会在done函数中处理返回值。
6.6.2.4 处理错误
正如上面所述,ABP在服务器端处理异常,并且返回一个带有错误消息的对象。如下所示:
{
"targetUrl": null,
"result": null,
"success": false,
"error": {
"message": "An internal error occured during your request!",
"details": "..."
},
"unAuthorizedRequest": false,
"__abp": true
}
正如你看到的,success是false 并且 result是null。abp.ajax处理这个对象,并且使用abp.message.error函数来显示错误消息给用户。如果你的服务器端代码抛出了 UserFriendlyException 类型的异常。它会直接的显示异常信息给用户。否则,它会隐藏实际的错误(将错误写入日志),并且显示一个标准的“服务器内部错误…”信息给用户。所有的这些都是ABP自动处理的。
你可能想为某个特别的AJAX调用禁止显示消息,那么添加 abpHandleError: false 到 abp.ajax的options。
HTTP状态码
在异常发生的时候,ABP会返回给定的HTTP状态码:
401:未经身份验证的请求(没有登录,但是服务器端需要身份验证);
403:未授权的请求;
500:所有其它类型的异常。
6.6.2.5 WrapResult和DontWrapResult特性
使用 WrapResult和DontWrapResult 特性,可以对控制器的某个action或者所有的action来控制包装。
ASP.NET MVC 控制器
如果返回的类型是 JsonResult(或者Task\
public class PeopleController : AbpController
{
[HttpPost]
[WrapResult(WrapOnSuccess = false, WrapOnError = false)]
public JsonResult SavePerson(SavePersonModel person)
{
//TODO: 保存新创建的person到数据库并且返回person的id
return Json(new {PersonId = 42});
}
}
作为一个快速开发方式,我们只能使用 [DontWrapResult] 特性在这个相同的示例上。
你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpMvc()…)。
ASP.NET Web API 控制器
如果action被成功执行,ABP 不会默认包装 Web API Action的返回结果。如果需要的话,你可以添加WrapResult特性到action或者控制器上。但是它会 包装异常。
你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()…)。
动态Web API层
默认 ABP会 包装 动态Web API层的所有方法。你可以在你应用服务的接口上使用 WrapResult和DontWrapResult 特性来改变这个行为。
你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()…)。
ASP.NET Core 控制器
ABP会自动包装JsonResult,ObjectRes以及那些没有实现IActionResult对象。详情请查阅ASP.NET Core文档。
你可以在启动配置里面改变这个默认的行为(使用 using Configuration.Modules.AbpAspNetCore()…)。
6.6.2.6 动态Web API层
虽然ABP提供了一种调用Ajax的简单机制,但是在真实世界的应用中,为每个Ajax调用编写javascript函数是很经典的。例如:
//创建一个抽象了Ajax调用的function
var savePerson = function(person) {
return abp.ajax({
url: '/People/SavePerson',
data: JSON.stringify(person)
});
};
//创建一个新的 person
var newPerson = {
name: 'Dougles Adams',
age: 42
};
//保存该person
savePerson(newPerson).done(function(data) {
abp.notify.success('created new person with id = ' + data.personId);
});
这是一个最佳实践,但是对每个AJAX调用函数都这样做,那是耗时且乏味的。对于应用服务和控制器,ABP能够自动的生成这些函数。
详情请阅读动态Web API层文档和ASP.NET Core文档。