OData的初步认识
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
OData的初步认识
What – OData是什么?
OData - Open Data Protocol,是⼀个设计和使⽤RESTful API的标准。
REST本⾝只是⼀个构建web服务的思想和理念,其没有规定⼀个统⼀的标准来限制开发⼈员该如何设计RESTful API。
其实我们实际开发中的确也没有遵循某个统⼀的标准去设计WebAPI。
因为⼤多数场景下,遵循⼀个统⼀的标准并不是必要的。
但在某些场景下,有这样⼀个标准却能带来很⼤的好处。
OData的理想是, ⽆论哪个组织构建的RESTful API,只要其符合OData标准。
其他组织就可以按照OData标准中定义的⽅式去使⽤这个API获取/修改资源。
这个可以类⽐SQL标准之于RDBMS关系。
⽆论什么关系型数据库,如果其声称⽀持SQL 标准,任何⼈就可以使⽤标准SQL查询语句来查询数据。
标准化的另⼀个好处:可以将Odata协议实现到⼀个通⽤的类库中,通过这个类库去创建和访问RESTful API可以减少开发⼈员的⼯作量。
官⽹上有很多这样的组件。
Who - 谁发布了OData?
该标准由微软发起,前三个版本1.0、2.0、3.0都是微软开放标准。
When - 什么时候成为了⼯业标准?
第四个版本4.0于2014年3⽉17⽇在OASIS投票通过成为开放⼯业标准
Why – 为什么需要OData?
OData是⼀个协议,⼀个标准。
所以这个问题等同于为什么我们需要协议。
类⽐TCP协议就可以理解⼀般。
假设你开发的组件必须要和某个第三⽅组件通信,如果第三⽅组件不⽀持TCP⽽只⽀持其内部开发的⼀个私有协议,你就肯定头⼤了,你必须在你的组件⾥单独为其实现这个私有协议。
如果⼤家都⽀持TCP协议,不就省事了么。
这就是标准协议的作⽤:协议和标准⽤于制定⼀个统⼀通⽤的规则。
我们只需要按照这个协议或标准⽣产组件,那么这个组件就可以⽅便的和其他组件集成/协作。
⽽⽆须根据其他组件的私有标准定制化组件。
前⾯说到Rest只是⼀种设计Web服务的思想,不是⼀种标准化的协议。
正由于缺乏标准化,从⽽导致各家公布的Restful API 统⼀通⽤⽅⾯的⽋缺。
OData就是为弥补这种⽋缺⽽被提出来的标准协议。
下⾯全是延伸阅读可略过。
Web服务有两种实现⽅式,⼀是SOAP协议⽅式,⼆是REST⽅式。
SOAP是⼀套完整的实现Web服务的解决⽅案。
这⾥有必要先简单了解SOAP⽅式的Web服务,然后对⽐SOAP⽅式,我们会发现REST⽅式⽋缺了什么。
SOAP⽅式的Web服务中的Web服务描述语⾔(WSDL)和简单对象访问协议(SOAP)⼀起构成了SOAP⽅式下的Web服务的结构单元。
客户端通过WSDL可以了解Web服务公开了那些可以被执⾏的⽅法以及Web服务可以发送或接收的消息格式(解决了公布访问资源⽅法的问题)。
客户端按照SOAP将调⽤位于远程系统上的服务所需信息序列化为消息(解决了如何调⽤远程⽅法的问题)。
注意WSDL描述的服务以及SOAP消息都是符合统⼀标准的,都是机器可读的.
WSDL基于XML格式,⽤来描述Web服务。
WSDL⽂档可以看成是客户端和服务器之间的⼀个协约。
使⽤WSDL⼯具,你可以⾃动处理这个过程,⼏乎不⽤⼿⼯编写代码就能够让应⽤程序整合新的服务。
因此WSDL是Web服务体系结构的基础,因为它提供了⼀个通⽤语⾔,⽤来描述服务和整合这些服务的平台。
SOAP本⾝提供了与Web服务交换信息的⽅法。
SOAP是序列化调⽤位于远程系统上的服务所需信息的标准⽅法,这些信息可以使⽤⼀种远程系统能够读懂的格式通过⽹络发送到远程系统,⽽不必关⼼远程系统运⾏于何种平台或者使⽤何种语⾔编写。
SOAP以XML格式提供了⼀个简单、轻量的⽤于在分散或分布环境中交换结构化和类型信息的机制。
实际上它通过提供⼀个有标准组件的包模型和在模块中编码数据的机制,定义了⼀个简单的表⽰应⽤程序语义的机制。
对照SOAP⽅式的Web服务,REST中没有⽤于描述资源(服务)列表,资源元数据的类似于WSDL的东东。
所以有⼈在2009年提出了⼀个标准WADL去描述REST⽅式的Web 服务,但⾄今没有被标准化。
个⼈认为使⽤WSDL/WADL去描述REST⽅式的Web服务太别扭,这是典型的RPC思路,⽽REST是⼀种把服务抽象为资源的架构思想。
⽤描述RPC的WSDL去描述REST⽅式的Web服务并不合适。
我们需要其他策略去代替WSDL实现“公布访问资源⽅法的问题”。
由于没有类似于SOAP的权威性协议作为规范,因此各个⽹站的REST实现都⾃有⼀套,也正是因为这种各⾃实现的情况,在性能和可⽤性上会⼤⼤⾼于SOAP发布的web service,但细节⽅⾯有太多没有约束的地⽅,其统⼀通⽤⽅⾯远远不及SOAP。
举个例⼦:假设A组织,B组织都实现了Restful API来通过⼯号查询⼈员信息,因为没有统⼀的规范。
A的API 可能是这样:
B的API 可能是这样:
第三⽅客户端在实现远程调⽤的时候就必须考虑这些API的差异,分别查看A,B的API⽂档。
如果有个权威性协议作为规范做指导,规定这个API应该实现成下⾯这样,那么第三⽅客户端也只需按照这个标准去调⽤远程API,⽽不⽤查看A,B的API⽂档:
解释了这么多,就是为了引出:OData是这样的⼀个设计和使⽤Restful API 的权威性协议. OData定义了⼀些标准规则(像⼀个接⼝定义⼀堆⽅法⼀样),实现Restful API时候,必须实现这些标准规则(就像实现⼀个接⼝必须实现其所有⽅法⼀样)。
第三⽅就可以根据Odata协议定义的规则去访问Restful API。
Where –什么样的场景下可以考虑使⽤OData?
并不是说你创建的所有RESTful API都需要符合OData协议。
只有在需要Open Data(开放数据给其他组织)时候,才有必要按照OData协议设计RESTful API。
这⾥的Open Data是指开放数据给第三⽅使⽤,并且你并不知道谁是第三⽅。
⽐如博客园的RSS,谁订阅了RSS,博客园是不清楚的。
如果你的数据只被你⾃家公司的客户端使⽤, OData就是⼀个可选项,你完全有理由不按照OData规范去设计RESTful API。
How – 如何使⽤OData?
⾸先看⼀下C#客户端调⽤符合OData标准的WebApi是多么的⽅便(官⽹/上也有js的类库)。
第⼀步,通过Nuget安装OData Client for .Net包。
第⼆步,安装VS插件:OData v4 Client Code Generator。
第三步:假设存在⼀个可⽤的WebApi(后⾯介绍如何创建) - http://localhost:33189/Odata. 我们修改代码模板中的MetadataDocumentUri如下,然后保存。
T4会访问http://localhost:33189/Odata获得资源的元数据,然后根据元数据⽣成资源对应的C#类。
T4可以怎么做是因为WebApi是按照OData的标准去公布资源列表和资源的元数据。
第四步:在我们的代码中就可以操作CLR对象来消费远程的webAPI了。
体验到Odata标准的⼒量了吧。
接下来看⼀下C#服务端如何实现上⾯客户端需要调⽤的OData的WebAPI,有两种⽅式,有点细微的差别。
第⼀步:创建⼀个空的WebApi项⽬。
第⼆步: 通过Nuget引⼊EF6 和 WebApi 2.2 for OData v4.0. 如下图。
第三步:创建Entity和DbContext类,以及配置数据库连接。
并通过enable migration完成数据库的创建,可在Configuration的seed的⽅法中,添加⼀些初始化的数据。
第四步:配置WebApiConfig如下
第五步:创建ProductsController
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using ;
using .Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using ODataAPI.Models;
using System.Web.OData;
namespace ODataAPI.Controllers
{
/*
To add a route for this controller, merge these statements into the Register method of the WebApiConfig class. Note that OData URLs are case sensitive. using System.Web.Http.OData.Builder;
using ODataAPI.Models;
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
*/
public class ProductsController : ODataController
{
private ODataAPIContext db = new ODataAPIContext();
// GET odata/Products
//[Queryable]
[EnableQuery]
public IQueryable<Product> GetProducts()
{
return db.Products;
}
// GET odata/Products(5)
//[Queryable]
[EnableQuery]
public SingleResult<Product> GetProduct([FromODataUri] int key)
{
return SingleResult.Create(db.Products.Where(product => product.ID == key));
}
// PUT odata/Products(5)
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (key != product.ID)
{
return BadRequest();
}
db.Entry(product).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(product);
}
// POST odata/Products
public async Task<IHttpActionResult> Post(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Created(product);
}
// PATCH odata/Products(5)
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> patch)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
patch.Patch(product);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(product);
}
// DELETE odata/Products(5)
public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
db.Products.Remove(product);
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool ProductExists(int key)
{
return db.Products.Count(e => e.ID == key) > 0;
}
}
}
View Code
第六步:F5运⾏,接着客户端就可以调⽤了。
可以通过访问和看看resource list 和元数据长什么样。
另外,我们可以通过VS的OData Controller模板来创建webAPIController(如下)。
注意使⽤这种⽅式创建webAPIController时,不可以导⼊WebApi 2.2 for OData v4.0这个类库,否则会出现dll 冲突。