多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 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调用示例: ```javascript 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](http://api.jquery.com/jQuery.ajax/) 函数中的参数。有一些默认参数:dataType 是 **json**,type是 **POST**,还有 contentType是 **application/json**(在发送数据到服务器端之前,我们需要调用 **JSON.stringify** 将脚本对象转换为JSON字符串)。通过对apb.ajax传递options可以覆盖默认值。 abp.ajax返回[promise](http://api.jquery.com/deferred.promise/)。因此,你可以写这些处理函数:done,fail,then等等。在这个例子中,我们对 **PeopleController's SavePerson action** 发送了一个简单的AJAX请求。在 **done** 处理函数中,我们对新创建的person取得了它的主键id并且显示了创建成功的通知。让我们看看 **MVC Controller**: ```csharp 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响应返回的内容应该像下面一样: ```json { "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在服务器端处理异常,并且返回一个带有错误消息的对象。如下所示: ```json { "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\<JsonResult\>)**,那么ABP会默认包装ASP.NET MVC action的返回结果。你可以使用 **WrapResult** 特性来改变它。如下所示: ```csharp 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]** 特性在这个相同的示例上。 你可以在[启动配置](225824)里面改变这个默认的行为(使用 Configuration.Modules.AbpMvc()...)。 #### ASP.NET Web API 控制器 如果action被成功执行,ABP **不会默认包装** Web API Action的返回结果。如果需要的话,你可以添加WrapResult特性到action或者控制器上。但是它会 **包装异常**。 你可以在[启动配置](225824)里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()...)。 #### 动态Web API层 **默认** ABP会 **包装** 动态Web API层的所有方法。你可以在你应用服务的接口上使用 **WrapResult和DontWrapResult** 特性来改变这个行为。 你可以在[启动配置](225824)里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()...)。 #### ASP.NET Core 控制器 ABP会自动包装JsonResult,ObjectRes以及那些没有实现IActionResult对象。详情请查阅[ASP.NET Core文档](225864)。 你可以在[启动配置](225824)里面改变这个默认的行为(使用 using Configuration.Modules.AbpAspNetCore()...)。 ### 6.6.2.6 动态Web API层 虽然ABP提供了一种调用Ajax的简单机制,但是在真实世界的应用中,为每个Ajax调用编写javascript函数是很经典的。例如: ```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调用函数都这样做,那是耗时且乏味的。对于[应用服务](225840)和控制器,ABP能够自动的生成这些函数。 详情请阅读[动态Web API层文档](225847)和[ASP.NET Core文档](225864)。