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 可能是這樣:http://A/api/person/001
B的API 可能是這樣:http://A/api/person/id=001
第三方客戶端在實現遠程調用的時候就必須考慮這些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是多么的方便(官網http://www.odata.org/上也有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 System.Net; using System.Net.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; } } }
第六步:F5運行,接着客戶端就可以調用了。可以通過訪問http://localhost:#/OData/ 和 http://localhost:#/OData/$metadata 看看resource list 和元數據長什么樣。
另外,我們可以通過VS的OData Controller模板來創建webAPIController(如下)。注意使用這種方式創建webAPIController時,不可以導入WebApi 2.2 for OData v4.0這個類庫,否則會出現dll沖突。