本篇介绍几个比较重要的概念,后续基于 OData Model 实现 CRUD。
- REST
REST (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 就是为弥补这种欠缺而被提出来的标准协议。
查看 Northwind OData Service
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 插件的查看效果:
以 json 格式显示数据
GET 请求默认以 xml 格式显示,在 URI 后加上 ?$format=json
则以 json 格式显示,如:http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/?$format=json 则以 json 格式显示。
元数据 ( metadata )
在 URI 后面加上 $metadata
则显示元数据:
Request:
Response:
<?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>
查看 Entity Set
Request:
- Type: GET
- URL: http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/Suppliers/?$format=json
Response:
{
"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
}
]
}
查看单条 Entity
比如,我们想查看第一个供应商:
Request:
- Type: GET
- URL: http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/Suppliers(0)/?$format=json
Response:
{
"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"
},
...
}
查看 Entity 的相关 Property
比如,查看第一个供应商的名称:
Request:
- Type: GET
- URL: http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/Suppliers(0)/Name?$format=json
Response:
{
"odata.metadata": "http://services.odata.org/V3/(S(i2ebiza3pghgcecz3upusotg))/OData/OData.svc/$metadata#Edm.String",
"value": "Exotic Liquids"
}
先了解这么多,还有 Navigation properties 等,以后再说。
OpenUI5 OData Model
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 属于服务器端的数据模型,也就是说,客户端必须请求后,根据服务器的应答,才能看到请求的数据。
Same-origin 政策
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
通过 OData model 读取 OData 数据
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");