文章目錄
使用 ASP.NET Web API 建立 OData 服務
OData 從 2013 年首次出現後就吸引不少技術人員目光,剛好當時專案允許立馬就嘗試,以當時的專案需要同時提供 API 給 WebSite、iOS 及 Android,同樣的資料內容在不同畫面上會因為裝置大小或是版面問題而需要不同欄位,透過 OData 來處理這樣的需求確實方便不少。
最近則是因為專案時程因素,所以預計先長個 OData API 讓前後端的資料可以先銜接起來,日後如果需要調整再來處理(據經驗,通常這樣的規劃在系統上線後就會變成系統穩定就不用改了XD),無論如何還是來紀錄一下如何建立 OData 服務吧
基本環境
- Visual Studio 2017
- .NET Framework 4.6.2
- ASP.NET Web API 2.2
- OData v4
- Entity Framework 6.1.3
建立 ASP.NET Web API 專案
建立專案 –> Web –> ASP.NET Web Application
ASP.NET Empty template –> Web API
選項一:使用 scaffolding 建立 OData 服務
Controllers 資料夾上按右鍵 –> Add –> Controller…
Controller –> Web API 2 OData v3 Controller with actions,using Entity Framework
指定
Model
、 context 與 Controller name
- 需要注意的是使用 scaffolding 來建立 OData 的做法僅限 OData v3 版本,
OData v4 並不支援
過程中會自動安裝以下套件
Microsoft.AspNet.WebApi.OData 5.3.1
2017/10/10 最新版為 5.7.0
Microsoft.Data.Edm 5.6.0
2017/10/10 最新版為 5.8.3
Microsoft.Data.OData 5.6.0
2017/10/10 最新版為 5.8.3
System.Spatial 5.6.0
2017/10/10 最新版為 5.8.3
選項二:手動建立 OData 服務
以下操作需先完成 EntityFramework 連線 DB 設定
Models –> Add –> ADO.NET Enityt Data Model
Code First from data base
Choose Data Connection
Choose Data Objects
安裝
Microsoft.AspNet.Odata
套件注意這邊的
Microsoft.AspNet.Odata
與上面使用的Microsoft.AspNet.WebApi.OData
不同Microsoft.AspNet.Odata
是 OData v4 用Microsoft.AspNet.WebApi.OData
則是 OData v1-v3 用使用
Package Manager Console
Install-Package Microsoft.AspNet.Odata
使用
NuGet Package Manager
如果安裝很久出現錯誤或是遲遲無法完成安裝,可以改用 VS2015 安裝看看,我卡了好久改用 VS2015 才成功
建立 OData controller
以測試 db - Northwind 為例
建立
ShippersController
加入
ShippersController.cs
public class ShippersController : ODataController { }
透過 EntityFramework 檢查資料
NorthwindEntities db = new NorthwindEntities(); private bool ShippersExists(int key) { return db.Shippers.Any(p => p.ShipperID == key); }
加上 Dispose
protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); }
完整程式碼
using System.Linq; using System.Web.OData; using TestOData.Models; namespace TestOData.Controllers { public class ShippersController: ODataController { NorthwindEntities db = new NorthwindEntities(); private bool ShippersExists(int key) { return db.Shippers.Any(p => p.ShipperID == key); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } }
註冊 OData 服務
- 開啟
App_Start/WebApiConfig.cs
註冊 OData routeing
將新增的程式碼加至 Register 最後
public static void Register(HttpConfiguration config) { // New code: ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Shippers>("Shippers"); config.MapODataServiceRoute( routeName: "ODataRoute", routePrefix: "odata", model: builder.GetEdmModel()); }
完整程式碼
using System.Net.Http.Formatting; using System.Web.Http; using System.Web.OData.Builder; using System.Web.OData.Extensions; using TestOData.Models; namespace TestOData { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services config.Formatters.Clear(); config.Formatters.Add(new JsonMediaTypeFormatter()); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // New code: ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Shippers>("Shippers"); config.MapODataServiceRoute( routeName: "ODataRoute", routePrefix: "odata", model: builder.GetEdmModel()); } } }
- 開啟
加入 CRUD
Get - Read
[EnableQuery] public IQueryable<Shippers> Get() { return db.Shippers; } [EnableQuery] public SingleResult<Shippers> Get([FromODataUri] int key) { IQueryable<Shippers> result = db.Shippers.Where(p => p.ShipperID == key); return SingleResult.Create(result); }
Post - Create
public async Task<IHttpActionResult> Post(Shippers shippers) { if (!ModelState.IsValid) { return BadRequest(ModelState); } db.Shippers.Add(shippers); await db.SaveChangesAsync(); return Created(shippers); }
Put/Patch - Update
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Shippers> shippers) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var entity = await db.Shippers.FindAsync(key); if (entity == null) { return NotFound(); } shippers.Patch(entity); try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ShippersExists(key)) { return NotFound(); } else { throw; } } return Updated(entity); } public async Task<IHttpActionResult> Put([FromODataUri] int key, Shippers update) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (key != update.ShipperID) { return BadRequest(); } db.Entry(update).State = EntityState.Modified; try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ShippersExists(key)) { return NotFound(); } else { throw; } } return Updated(update); }
Delete - Delete
public async Task<IHttpActionResult> Delete([FromODataUri] int key) { var product = await db.Shippers.FindAsync(key); if (product == null) { return NotFound(); } db.Shippers.Remove(product); await db.SaveChangesAsync(); return StatusCode(HttpStatusCode.NoContent); }
完整程式碼
using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Web.Http; using System.Web.OData; using TestOData.Models; namespace TestOData.Controllers { public class ShippersController: ODataController { NorthwindEntities db = new NorthwindEntities(); [EnableQuery] public IQueryable<Shippers> Get() { return db.Shippers; } [EnableQuery] public SingleResult<Shippers> Get([FromODataUri] int key) { IQueryable<Shippers> result = db.Shippers.Where(p => p.ShipperID == key); return SingleResult.Create(result); } public async Task<IHttpActionResult> Post(Shippers shippers) { if (!ModelState.IsValid) { return BadRequest(ModelState); } db.Shippers.Add(shippers); await db.SaveChangesAsync(); return Created(shippers); } public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Shippers> shippers) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var entity = await db.Shippers.FindAsync(key); if (entity == null) { return NotFound(); } shippers.Patch(entity); try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ShippersExists(key)) { return NotFound(); } else { throw; } } return Updated(entity); } public async Task<IHttpActionResult> Put([FromODataUri] int key, Shippers update) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (key != update.ShipperID) { return BadRequest(); } db.Entry(update).State = EntityState.Modified; try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ShippersExists(key)) { return NotFound(); } else { throw; } } return Updated(update); } public async Task<IHttpActionResult> Delete([FromODataUri] int key) { var product = await db.Shippers.FindAsync(key); if (product == null) { return NotFound(); } db.Shippers.Remove(product); await db.SaveChangesAsync(); return StatusCode(HttpStatusCode.NoContent); } private bool ShippersExists(int key) { return db.Shippers.Any(p => p.ShipperID == key); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } }
實際效果
Get
http://localhost:6600/odata/Shippers
Post
http://localhost:6600/odata/Shippers
Patch
http://localhost:6600/odata/Shippers(5)
Put
http://localhost:6600/odata/Shippers(5)
Delete
http://localhost:6600/odata/Shippers(5)
心得
這篇筆記花了好幾天才寫完,陸陸續續遇到不少問題,也卡關了好幾次,但終究是搞定了~~ 呼
雖然不知道原因,但可以確定的是 OData v4 有一些跟之前版本不同的 breaking change 讓使用過之前版本的工程師佔不到便宜,還好趁著這次紀錄建立過程釐清不少相關設定,降低系統上線時因為不熟悉設定造成問題的機率
僅管對於設定有比較了解一些,但對於相關原理及進階使用方式的掌握度還是有待加強,相信隨著專案的進行會一步一步更加清晰,到時有什麼值得紀錄介紹的再跟大家分享
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-03
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。