本篇介绍几个比较重要的概念,后续基于 OData Model 实现 CRUD。
RESTREST (Representational State Transfer) 这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的,翻译成中文大意为表现层状态传输。由于他是 HTTP 协议(1.0 版和 1.1 版)的主要设计者、Apache 服务器软件的作者之一、Apache 基金会的第一任主席,所以 REST 原则迅速流行起来。当一个软件架构符合 REST 原则,我们称之为 RESTful 架构。
OData开放数据协议(Open Data Protocol,缩写 OData)是一种描述如何创建和访问 Restful 服务的 OASIS 标准。该标准由微软发起,前三个版本1.0、2.0、3.0 都是微软开放标准。第四个版本4.0 于 2014 年 3 月 17 日在 OASIS 投票通过成为开放工业标准。
OData 是用来查询和更新数据的一种 Web协议,提供了把存在于应用程序中的数据暴露出来的方式。OData 运用且构建于很多 Web 技术之上,比如 HTTP、Atom Publishing Protocol(AtomPub)和 JSON,提供了从各种应用程序、服务和存储库中访问信息的能力。OData 被用来从各种数据源中暴露和访问信息, 这些数据源包括但不限于:关系数据库、文件系统、内容管理系统和传统 Web 站点。
前面说到 Rest 只是一种设计 Web 服务的思想,不是一种标准化的协议。正由于缺乏标准化,从而导致各家公布的 Restful API 统一通用方面的欠缺。OData 就是为弥补这种欠缺而被提出来的标准协议。
http://services.odata.org/ 这个网站提供了 OData data service 的示例,并且可以对 OData 数据进行 CRUD 操作。我们首先通过查看这些数据,了解 OData 的知识点。
我们先在浏览器中输入 http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/, 网站以 xml 格式提供了 Northwind 示例数据。这个是 Microsoft 经常使用的一个示例数据库,MS Office 套件中的 Access 数据库也可以看到。为了方便数据查看,建议使用 Chrome 的插件:Postman。Chrome 在查看 xml 和 json 数据格式上,比较难看。或者使用 IE 浏览器。以下是使 Postman 插件的查看效果:
GET 请求默认以 xml 格式显示,在 URI 后加上 ?$format=json 则以 json 格式显示,如:http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/?$format=json 则以 json 格式显示。
在 URI 后面加上 $metadata 则显示元数据:
Request:
type: GETuri:http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/$metadataResponse:
<?xml version="1.0" encoding="utf-8"?> <edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"> <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> <Schema Namespace="ODataDemo" xmlns="http://schemas.microsoft.com/ado/2009/11/edm"> ... <EntityType Name="Supplier"> <Key> <PropertyRef Name="ID" /> </Key> <Property Name="ID" Type="Edm.Int32" Nullable="false" /> <Property Name="Name" Type="Edm.String" /> <Property Name="Address" Type="ODataDemo.Address" /> <Property Name="Location" Type="Edm.GeographyPoint" SRID="Variable" /> <Property Name="Concurrency" Type="Edm.Int32" ConcurrencyMode="Fixed" Nullable="false" /> <NavigationProperty Name="Products" Relationship="ODataDemo.Product_Supplier_Supplier_Products" ToRole="Product_Supplier" FromRole="Supplier_Products" /> </EntityType> <ComplexType Name="Address"> <Property Name="Street" Type="Edm.String" /> <Property Name="City" Type="Edm.String" /> <Property Name="State" Type="Edm.String" /> <Property Name="ZipCode" Type="Edm.String" /> <Property Name="Country" Type="Edm.String" /> </ComplexType> ... <EntityContainer Name="DemoService" m:IsDefaultEntityContainer="true"> ... <EntitySet Name="Suppliers" EntityType="ODataDemo.Supplier" /> ... <FunctionImport Name="GetProductsByRating" ReturnType="Collection(ODataDemo.Product)" EntitySet="Products" m:HttpMethod="GET"> <Parameter Name="rating" Type="Edm.Int16" Nullable="false" /> </FunctionImport> <AssociationSet Name="Products_Advertisement_Advertisements" Association="ODataDemo.FeaturedProduct_Advertisement_Advertisement_FeaturedProduct"> <End Role="FeaturedProduct_Advertisement" EntitySet="Products" /> <End Role="Advertisement_FeaturedProduct" EntitySet="Advertisements" /> </AssociationSet> <AssociationSet Name="Products_Categories_Categories" Association="ODataDemo.Product_Categories_Category_Products"> ... </AssociationSet> ... </EntityContainer> <Annotations Target="ODataDemo.DemoService"> <ValueAnnotation Term="Org.OData.Display.V1.Description" String="This is a sample OData service with vocabularies" /> </Annotations> ... </Schema> </edmx:DataServices> </edmx:Edmx>为了更清楚看出文档结构,我省略了不相关的部分。 介绍一下元数据的重点。元数据用于定义 Odata 的数据结构。下面的图来自 [Manually creating a data model to use in SAP Web IDE’s Mock Data server] ( https://www.sap.com/developer/tutorials/hcp-webide-create-odata-model.html),能够很好地说明 metadata 的构成:
dataServiceVesion: data service 的版本EntitiContainer 总体确定包括那些 EntitySet,比如 Northwind 包括 Products, Suppliers 等等。 <EntityContainer Name="DemoService" m:IsDefaultEntityContainer="true"> <EntitySet Name="Products" EntityType="ODataDemo.Product" /> <EntitySet Name="ProductDetails" EntityType="ODataDemo.ProductDetail" /> <EntitySet Name="Categories" EntityType="ODataDemo.Category" /> <EntitySet Name="Suppliers" EntityType="ODataDemo.Supplier" /> <EntitySet Name="Persons" EntityType="ODataDemo.Person" /> <EntitySet Name="PersonDetails" EntityType="ODataDemo.PersonDetail" /> <EntitySet Name="Advertisements" EntityType="ODataDemo.Advertisement" /> ... </EntityContainer> EntitySet 下包含 EntityType,比如 Suppliers 这个 Set 下面包含 Supplier 这个 Entity。Entity 中包含 key 和 Properties: <EntityType Name="Supplier"> <Key> <PropertyRef Name="ID" /> </Key> <Property Name="ID" Type="Edm.Int32" Nullable="false" /> <Property Name="Name" Type="Edm.String" /> <Property Name="Address" Type="ODataDemo.Address" /> <Property Name="Location" Type="Edm.GeographyPoint" SRID="Variable" /> <Property Name="Concurrency" Type="Edm.Int32" ConcurrencyMode="Fixed" Nullable="false" /> <NavigationProperty Name="Products" Relationship="ODataDemo.Product_Supplier_Supplier_Products" ToRole="Product_Supplier" FromRole="Supplier_Products" /> </EntityType> <ComplexType Name="Address"> <Property Name="Street" Type="Edm.String" /> <Property Name="City" Type="Edm.String" /> <Property Name="State" Type="Edm.String" /> <Property Name="ZipCode" Type="Edm.String" /> <Property Name="Country" Type="Edm.String" /> </ComplexType>Request:
Type: GETURL: http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/Suppliers/?$format=jsonResponse:
{ "odata.metadata": "http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/$metadata#Suppliers", "value": [ { "ID": 0, "Name": "Exotic Liquids", "Address": { "Street": "NE 228th", "City": "Sammamish", "State": "WA", "ZipCode": "98074", "Country": "USA" }, "Location": { "type": "Point", "coordinates": [ -122.03547668457, 47.6316604614258 ], "crs": { "type": "name", "properties": { "name": "EPSG:4326" } } }, "Concurrency": 0 }, { "ID": 1, "Name": "Tokyo Traders", "Address": { "Street": "NE 40th", "City": "Redmond", "State": "WA", "ZipCode": "98052", "Country": "USA" }, "Location": { "type": "Point", "coordinates": [ -122.107711791992, 47.6472206115723 ], "crs": { "type": "name", "properties": { "name": "EPSG:4326" } } }, "Concurrency": 0 } ] }比如,我们想查看第一个供应商:
Request:
Type: GETURL: http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/Suppliers(0)/?$format=jsonResponse:
{ "odata.metadata": "http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/$metadata#Suppliers/@Element", "ID": 0, "Name": "Exotic Liquids", "Address": { "Street": "NE 228th", "City": "Sammamish", "State": "WA", "ZipCode": "98074", "Country": "USA" }, ... }比如,查看第一个供应商的名称:
Request:
Type: GETURL: http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/Suppliers(0)/Name?$format=jsonResponse:
{ "odata.metadata": "http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/$metadata#Edm.String", "value": "Exotic Liquids" }先了解这么多,还有 Navigation properties 等,以后再说。
SAP 提供了 sap.ui.model.odata.ODataModel,sap.ui.model.odata.v2.ODataModel 和 sap.ui.model.odata.v4.ODataModel。sap.ui.model.odata.ODataModel 已经过时。Odata v2 model 目前支持到 OData 2.0。Odata v4 model 支持到 OData 4.0,但仅支持绑定模式。不支持代码模式,应该还在开发过程中。建议使用 Odata v2 model。
Odata v2 model 和 Odata model 的变化和区别请参见:OData v2 Model
OData Model 属于服务器端的数据模型,也就是说,客户端必须请求后,根据服务器的应答,才能看到请求的数据。
OData 是一种基于 Web 的协议,数据访问受到 Same-origin policy 的限制。什么是 Same-origin policy? 根据 WIKI 的解释:
In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number. This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page’s Document Object Model.
并且给出了一些示例方便我们理解:
如果我们直接对 services.odata.com 的进行 CRUD,因为违背了Same-origin 策略,会产生错误。解决办法:
使用代理,比如https://cors-anywhere.herokuapp.com/在 SAP Web IDE 中通过 HCP (Hana Cloud Platform) 账号,由 SAP Web IDE 代理。参考帖子:stackoverflow: access cross origin resources
v2 模型提供了两种方法,一是通过代码,二是通过数据绑定。我们先来看通过代码如何访问 OData 数据:
var sServiceUrl = "http://services.odata.org/V3/Northwind/Northwind.svc/"; var oModel = new sap.ui.model.odata.v2.ODataModel(sServiceUrl); sap.ui.getCore().setModel(oModel); oModel.read("/Products(1)", { success: function(oData, oResponse){ console.log(oData); console.log(oResponse); }, error: function(oError){ console.log(oError); } })运行程序,Chrome 浏览器返回如下错误( F12 查看)
Failed to load resource: the server responded with a status of 501 (Not Implemented) index.html:1 XMLHttpRequest cannot load http://services.odata.org/V3/Northwind/Northwind.svc/$metadata. Response for preflight has invalid HTTP status code 501将 var sServiceUrl = "http://services.odata.org/V3/Northwind/Northwind.svc/"; 语句改为:
var sServiceUrl = "https://cors-anywhere.herokuapp.com/http://services.odata.org/V3/Northwind/Northwind.svc/";可以正常返回 oData 和 oResponse 。
第二种方法是通过控件绑定 OData 数据:
var sServiceUrl = "https://cors-anywhere.herokuapp.com/http://services.odata.org/V3/Northwind/Northwind.svc/"; var oModel = new sap.ui.model.odata.v2.ODataModel(sServiceUrl); sap.ui.getCore().setModel(oModel); var oText = new sap.m.Text({ text: "Product name: {ProductName}" }); var oPage = new sap.m.Page("masterPage", { title: "Product 1 information", content: [oText] }); oPage.bindElement("/Products(1)"); var oApp = new sap.m.App(); oApp.addPage(oPage); oApp.placeAt("content");