如何使用Dojo的JsonRestStore连接REST服务?

日期: 2012-01-08 作者:Nick Maynard 来源:TechTarget中国 英文

  Dojo的数据抽象层dojo.data,为Dojo提供一个标准API来访问各种后端服务。JsonRestStore,位于Dojo扩展库DojoX之中,允许您将您的应用程序快速连接到后端具象状态传输(Representational State Transfer,REST服务。

  JsonRestStore是链接Dojo到REST服务的一个优秀的解决方案。不过在默认情况下,JsonRestStore对传递给服务和由服务返回的信息拥有预期的格式。用于定制这些交互的Service Mapping Description (SMD)设备功能有限,且使用方法比较难以理解。

  JsonRestStore是一个数据存储,使用GET、PUT、POST和DELETE命令,通过基于与服务器交互的HTTP/REST标准提供完整的读取、写入和通知功能。它允许您使用Dojo Data API和JavaScript与服务器端数据库/持久性数据存储通信,以及高效地处理创建、读取、更新和删除(CRUD)操作。 当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

  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文档。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐