内容


使用 Dojo 的 JsonRestStore 连接您的 REST 服务

学习一种简单、通用的服务实现

Comments

简介

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 能力。调用生命周期是:

  1. 构建一个含有 URL、参数和数据格式元数据的标准 Dojo XHR 调用结构。
  2. 调用该方法的参数转换函数。该方法可以改变调用结构(包括路径),且可用于转换调用结构,以符合 REST 服务期望。
  3. 通过将内容节点转换为一个 JSON 形式,填入调用结构的 putData/postData/deleteData/getData 节点。
  4. 使用改变的 XHR 调用结构调用 XHR 方法。
  5. 添加一个结果转换器到 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

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 预期的完整数据结构的更多信息,见 参考资料 小节。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Java technology
ArticleID=778856
ArticleTitle=使用 Dojo 的 JsonRestStore 连接您的 REST 服务
publish-date=12052011