Dojo 的数据抽象层 dojo.data,为 Dojo 提供一个标准 API 来访问各种后端服务。JsonRestStore,位于 Dojo 扩展库 DojoX 之中,允许您将您的应用程序快速连接到后端具象状态传输(Representational State Transfer,REST)服务。
JsonRestStore 是链接 Dojo 到 REST 服务的一个优秀的解决方案。不过在默认情况下,JsonRestStore 对传递给服务和由服务返回的信息拥有预期的格式。用于定制这些交互的 Service Mapping Description (SMD) 设备功能有限,且使用方法比较难以理解。
当 Dojo 应用程序创建者想要与一个预先存在的后端 REST 存储通信时,如果储存的通信方式不能改变,它们使用一个自定义的 dojox.rpc.Service 实现。
在本文中,学习一个简单、通用的服务实现,用于将非标准 REST 服务连接到 JsonRestStore。通过一个实际示例研究如何使用和扩展它用于您自己的服务。
要下载本文源代码,见 下载 部分。
EasyRestService 提供 4 个 REST 操作的实现:POST (create)、GET (read)、PUT (update) 和 DELETE (delete)。对于每个活动,它在服务方法调用之前和之后提供 hook 能力。调用生命周期是:
- 构建一个含有 URL、参数和数据格式元数据的标准 Dojo XHR 调用结构。
- 调用该方法的参数转换函数。该方法可以改变调用结构(包括路径),且可用于转换调用结构,以符合 REST 服务期望。
- 通过将内容节点转换为一个 JSON 形式,填入调用结构的 putData/postData/deleteData/getData 节点。
- 使用改变的 XHR 调用结构调用 XHR 方法。
- 添加一个结果转换器到 XHR 回调函数。这个方法可以改变结果的数据结构。使用它可以将结果转换成 JsonRestStore 预期的结构。
清单 1 显示了示例代码。
清单 1. EasyRestService 源代码
dojo.provide("com.ibm.developerworks.EasyRestService");
(function() {
var pa = com.ibm.developerworks.EasyRestService = function (path, serviceImpl, schema) {
// Enforce the dojox.rpc.Rest trailing slash functionality
path = path.match(/\/$/) ? path : (path + '/');
// A dojox.rpc.Service implementation is a function with 3 function members
var service;
// GET function
service = function(id, args) {
return _execXhr("get", id, args);
};
// POST function member
service['post'] = function(id, value) {
return _execXhr("post", id, value);
};
// PUT function member
service['put'] = function(id, value) {
return _execXhr("put", id, value);
};
// DELETE function member
service['delete'] = function(id) {
return _execXhr("delete", id);
};
// Generic XHR function for all methods
var _execXhr = function(method, id, content) {
// Transform the method string
var methodCapitalised = method.substring(0,1).toUpperCase()
+ method.substring(1).toLowerCase();
var methodUpperCase = method.toUpperCase();
var methodLowerCase = method.toLowerCase();
// Get the transformer functions
var argumentsTransformer = service["transform" + methodCapitalised + "Arguments"];
var resultTransformer = service["transform" + methodCapitalised + "Results"];
// Construct the standard query
var serviceArgs = {
url : path + (dojo.isObject(id) ? '?' + dojo.objectToQuery(id) :
(id == null ? "" : id)),
handleAs : "json",
contentType : "application/json",
sync : false,
headers : { Accept : "application/json,application/javascript" }
};
// Transform the arguments
// NOTE: argumentsTransformer has a reference to "service"
serviceArgs = argumentsTransformer(serviceArgs, arguments);
// Copy the content into the appropriate *Data arg
// getData, putData, postData, deleteData
// NOTE: If you want your arguments transformer to edit the *Data arg directly,
// move the arguments transformer invocation to after this call
serviceArgs[methodLowerCase + 'Data'] = content;
// Kick off the call
var xhrFunction = dojo['xhr' + methodCapitalised];
var deferred = xhrFunction(serviceArgs);
// Add our result transformer
// NOTE: resultTransformer has a reference to "service" too
deferred.addCallback(dojo.partial(resultTransformer, deferred));
return deferred;
};
// Mix in the service hooks
// Uses a "default" implementation that does nothing
// Service hooks will have a reference to the "service" object in their context
dojo.mixin(service,
new com.ibm.developerworks.EasyRestService.DefaultHooks(),
serviceImpl);
// Now remove any default _constructor() methods
// This is necessary as the JsonRestStore stack uses _constructor() differently
delete service['_constructor'];
// Remove the declaredClass member if it has been added
delete service['declaredClass'];
// Save the path away
service.servicePath = path;
// Save the schema
service._schema = schema;
return service;
};
})();
dojo.declare("com.ibm.developerworks.EasyRestService.DefaultHooks", null, {
transformGetArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformPutArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformPostArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformDeleteArguments: function(serviceArgs) {
// Alter serviceArgs to provide the information the backend
// service requires
return serviceArgs;
},
transformGetResults: function(deferred, results) {
/*
* JsonRestStore expects the following format:
* [
* { id: "1", ... },
* { id: "2", ... },
* ...
* ]
*/
return results;
},
transformPutResults: function(deferred, results) {
/*
* JsonRestStore does not expect any specific content here
*/
return results;
},
transformPostResults: function(deferred, results) {
/*
* JsonRestStore expects:
* 1) A "Location" response header with location of the new item.
* From the Dojo API:
* The server’s response includes a Location header
* that indicates the id of the newly created object.
* This id will be used for subsequent PUT and DELETE
* requests. JsonRestStore also includes a
* Content-Location header that indicates the temporary
* randomly generated id used by client, and this
* location is used for subsequent PUT/DELETEs if no
* Location header is provided by the server or if
* a modification is sent prior to receiving a response
* from the server.
* NB: There is no JS method for altering response headers.
* You might wish to try overriding the
* deferred.ioArgs.xhr.getResponseHeader() method with your
* own implementation.
* 2) The new item in the following format:
* { id: "1", ... }
*/
return results;
},
transformDeleteResults: function(deferred, results) {
/*
* JsonRestStore does not expect any specific content here
*/
return results;
}
});
|
清单 1 中的代码复制 dojox.rpc.Rest 的默认功能。可以同 JsonRestStore 一起使用,如 清单 2 所示
清单 2. 默认情况下,
EasyRestService 和 JsonRestStore 的使用
dojo.require("com.ibm.developerworks.EasyRestService");
dojo.require("dojox.data.JsonRestStore");
var store = new dojox.data.JsonRestStore({
service: new com.ibm.developerworks.EasyRestService("https://mydomain.com/restservice"),
idAttribute : "id"
}); |
默认情况下, EasyRestService 实例不能以任何方式改变参数和结果。相反可以改变 DefaultHooks 来执行所需的参数和结果转换,EasyRestService 根据每个实例(per-instance)基础提供一个机制来重写这些转换函数。
EasyRestService 提供一个简单的机制来提供自定义转换函数。清单 3 中的示例修改了 EasyRestService 行为来在执行之前修改 GET 调用结构。
清单 3. 自定义
EasyRestService
dojo.require("com.ibm.developerworks.EasyRestService");
var transformers = {
transformGetArguments: function(args) {
console.log(args);
return args;
}
};
var service = new com.ibm.developerworks.EasyRestService(
"https://mydomain.com/restservice", transformers);
|
类似地,DefaultHooks 中的所有转换函数可以依据每个实例基础进行重写。
创建两个 EasyRestService 示例:一个是针对遵从服务的,另一个是针对非遵从服务的。本例使用它们作为 JsonRestStore 这两个实例的服务提供者,并根据存储获取基础提取。
在 清单 4 和 清单 5 中,使用两个含有 JSON 结构的只读文件对服务进行模拟。
清单 4. 遵从性 REST 服务,compliantService.json
[
{ id: 1, name: "Phil" },
{ id: 2, name: "John" }
] |
清单 5. 非遵从 REST 服务,noncompliantService.json
{ items : [
{ id: 1, name: "Phil" },
{ id: 2, name: "John" }
] } |
JavaScript 将使用 清单 6 中的代码实例化和查询存储。
清单 6. JavaScript 代码与存储交互
// Create a store using a service that needs no transformations
compliantStore = new dojox.data.JsonRestStore({
service : new com.ibm.developerworks.EasyRestService(
"./compliantService.json"),
idAttribute : "id"
});
// Cause an async fetch from the compliant service
dojo.create("p", {
innerHTML : "Requesting from compliant service"
}, dojo.body(), "last");
compliantStore.fetch({
onComplete : function(items, request) {
console.log(items);
// Log the number of items fetched
dojo.create("p", {
innerHTML : "Got " + items.length + " items from compliant service."
}, dojo.body(), "last");
}
});
// Create a store using a service which needs transformations
// to interpret the results
noncompliantStore = new dojox.data.JsonRestStore({
service : new com.ibm.developerworks.EasyRestService(
"./noncompliantService.json", {
transformGetResults : function(deferred, results) {
// This store wraps its results in an items object
// so return the items object
return results.items;
}
}),
idAttribute : "id"
});
// Cause an async fetch from the noncompliant service
dojo.create("p", {
innerHTML : "Requesting from noncompliant service"
}, dojo.body(), "last");
noncompliantStore.fetch({
onComplete : function(items, request) {
console.log(items);
// Log the number of items fetched
dojo.create("p", {
innerHTML : "Got " + items.length
+ " items from noncompliant service."
}, dojo.body(), "last");
}
}); |
在本文中,您学习了如何将您自己的 REST 服务链接到 JsonRestStore。示例显示了一个通过转换您的服务接口来提供 JsonRestStore 所需的签名的简单方法。对于那些有 JsonRestStore 所期望的完整数据结构的信息,您应该参考 DefaultHooks(DojoCampus 中的 JsonRestStore 文档)中的注释和 API 文档。
JsonRestStore 预期的完整数据结构的更多信息,见 参考资料 小节。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| EasyRestService 代码样例 - 项目归档文件 | EasyRestServiceExample.zip | 6.5KB | HTTP |
学习
- 阅读针对 JsonRestStore 的 DojoCampus 文档。
- 研究 JsonRestStore 的 Dojo API。JsonRestStore 促使所有保存的修改被发送到服务器,通过 REST 命令(GET、PUT、POST 或 DELETE)实现。
-
developerWorks Web development
专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
-
developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
-
developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还可以通过 Web 2.0 新手入门 栏目,迅速了解 Web 2.0 的相关概念。
- 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。
获得产品和技术
- IBM 产品评估试用版软件 或 IBM SOA 人员沙箱,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- 分享您的知识:加入一个关注 web 主题的 developerWorks 小组。
- Roland Barcia 在其博客中讨论了 Web 2.0 和中间件。
- 关注 developerWorks 成员 关于 web 主题的共享书签。
- 快速找到答案:访问 Web 2.0 Apps 论坛。
- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
