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调用示例:

  1. var newPerson = {
  2. name: 'Dougles Adams',
  3. age: 42
  4. };
  5. abp.ajax({
  6. url: '/People/SavePerson',
  7. data: JSON.stringify(newPerson)
  8. }).done(function(data) {
  9. abp.notify.success('created new person with id = ' + data.personId);
  10. });

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

  1. public class PeopleController : AbpController
  2. {
  3. [HttpPost]
  4. public JsonResult SavePerson(SavePersonModel person)
  5. {
  6. //TODO: 保存新创建的person到数据库并且返回person的id
  7. return Json(new {PersonId = 42});
  8. }
  9. }

正如你猜测的 SavePersonModel 包含了Name和Age属性。SavePerson 被标记为 HttpPost 特性,因为abp.ajax默认方法是POST。通过返回了匿名对象简化了方法实现。

这个看上去很简单直白,但是ABP在背后做了很多重要的处理。让我们深入了解一下:

6.6.2.3 AJAX 返回消息

即使我们直接的返回了一个带有PersonId = 2 的对象,ABP也会使用 MvcAjaxResponse 对象来包装它。事实上AJAX响应返回的内容应该像下面一样:

  1. {
  2. "success": true,
  3. "result": {
  4. "personId": 42
  5. },
  6. "error": null,
  7. "targetUrl": null,
  8. "unAuthorizedRequest": false,
  9. "__abp": true
  10. }

在这里所有的属性都是驼峰命名的(因为这在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在服务器端处理异常,并且返回一个带有错误消息的对象。如下所示:

  1. {
  2. "targetUrl": null,
  3. "result": null,
  4. "success": false,
  5. "error": {
  6. "message": "An internal error occured during your request!",
  7. "details": "..."
  8. },
  9. "unAuthorizedRequest": false,
  10. "__abp": true
  11. }

正如你看到的,success是false 并且 result是null。abp.ajax处理这个对象,并且使用abp.message.error函数来显示错误消息给用户。如果你的服务器端代码抛出了 UserFriendlyException 类型的异常。它会直接的显示异常信息给用户。否则,它会隐藏实际的错误(将错误写入日志),并且显示一个标准的“服务器内部错误…”信息给用户。所有的这些都是ABP自动处理的。

你可能想为某个特别的AJAX调用禁止显示消息,那么添加 abpHandleError: falseabp.ajax的options

HTTP状态码

在异常发生的时候,ABP会返回给定的HTTP状态码:

  • 401:未经身份验证的请求(没有登录,但是服务器端需要身份验证);

  • 403:未授权的请求;

  • 500:所有其它类型的异常。

6.6.2.5 WrapResult和DontWrapResult特性

使用 WrapResult和DontWrapResult 特性,可以对控制器的某个action或者所有的action来控制包装。

ASP.NET MVC 控制器

如果返回的类型是 JsonResult(或者Task\),那么ABP会默认包装ASP.NET MVC action的返回结果。你可以使用 WrapResult 特性来改变它。如下所示:

  1. public class PeopleController : AbpController
  2. {
  3. [HttpPost]
  4. [WrapResult(WrapOnSuccess = false, WrapOnError = false)]
  5. public JsonResult SavePerson(SavePersonModel person)
  6. {
  7. //TODO: 保存新创建的person到数据库并且返回person的id
  8. return Json(new {PersonId = 42});
  9. }
  10. }

作为一个快速开发方式,我们只能使用 [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函数是很经典的。例如:

  1. //创建一个抽象了Ajax调用的function
  2. var savePerson = function(person) {
  3. return abp.ajax({
  4. url: '/People/SavePerson',
  5. data: JSON.stringify(person)
  6. });
  7. };
  8. //创建一个新的 person
  9. var newPerson = {
  10. name: 'Dougles Adams',
  11. age: 42
  12. };
  13. //保存该person
  14. savePerson(newPerson).done(function(data) {
  15. abp.notify.success('created new person with id = ' + data.personId);
  16. });

这是一个最佳实践,但是对每个AJAX调用函数都这样做,那是耗时且乏味的。对于应用服务和控制器,ABP能够自动的生成这些函数。

详情请阅读动态Web API层文档ASP.NET Core文档