SAPUI5 (25) - 了解 OData 和 OpenUI5 的 OData Model

本篇介绍几个比较重要的概念,后续基于 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 的构成:

image
  • 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:

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:

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:

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.ODataModelsap.ui.model.odata.v2.ODataModelsap.ui.model.odata.v4.ODataModelsap.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

image

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.

并且给出了一些示例方便我们理解:

image

如果我们直接对 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/";

可以正常返回 oDataoResponse

第二种方法是通过控件绑定 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");

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容