使用 ASP.NET Web API 建立 OData v4 端點
開放式資料協定 (OData) 是一種網路資料存取協定。 OData 提供了透過 CRUD 操作 (建立、讀取、更新和刪除) 查詢和操作資料集的統一方法。
ASP.NET Web API 支援 v3 和 v4 通訊協定。 您甚至可以擁有與 v3 端點並行執行的 v4 端點。
本教學課程示範如何建立支援 CRUD 操作的 OData v4 端點。
教學課程中使用的軟體版本
- Web API 5.2
- OData v4
- Visual Studio 2017 (在這裡下載 Visual Studio 2017)
- Entity Framework 6
- .NET 4.7.2
教學課程版本
對於 OData 版本 3,請參閱「建立 OData v3 端點」。
建立 Visual Studio 專案
在 Visual Studio 中,從「檔案」功能表中選擇「新增>專案」。
展開「已安裝的>Visual C#>Web」,然後選擇「ASP.NET Web 應用程式 (.NET Framework)」範本。 將專案命名為 「ProductService」。
選取 [確定]。
選取空白範本。 在新增資料夾和核心參考:下,選擇 Web API。 選取 [確定]。
安裝 OData 套件
從 [工具] 功能表中,選取 [NuGet 套件管理員]> [套件管理員主控台]。 在「套件管理員主控台」視窗中,鍵入:
Install-Package Microsoft.AspNet.Odata
此命令會安裝最新的 OData NuGet 套件。
新增模型類別
模型是代表應用程式中的資料實體的物件。
在「方案總管」中,以滑鼠右鍵按一下「模型」資料夾。 從上下文功能表中選擇「新增>類別」。
注意
依照慣例,模型類別會放置在 Models 資料夾中,但您不必在自己的專案中遵循此慣例。
將類別命名為 Product
。 在 Product.cs 檔案中,將樣板程式碼替換為以下內容:
namespace ProductService.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
}
Id
屬性是實體索引鍵。 用戶端可以透過索引鍵查詢實體。 例如,要取得 ID 為 5 的產品,URI 為 /Products(5)
。 Id
屬性也將成為後端資料庫中的主索引鍵。
啟用 Entity Framework
在本教學課程中,我們將使用 Entity Framework (EF) Code First 建立後端資料庫。
注意
Web API OData 不需要 EF。 使用任何可以將資料庫實體轉換為模型的資料存取層。
首先,安裝 EF 的 NuGet 套件。 從 [工具] 功能表中,選取 [NuGet 套件管理員]> [套件管理員主控台]。 在「套件管理員主控台」視窗中,鍵入:
Install-Package EntityFramework
開啟 Web.config 檔案,然後在設定元素內的 configSections 元素後面新增以下部分。
<configuration>
<configSections>
<!-- ... -->
</configSections>
<!-- Add this: -->
<connectionStrings>
<add name="ProductsContext" connectionString="Data Source=(localdb)\mssqllocaldb;
Initial Catalog=ProductsContext; Integrated Security=True; MultipleActiveResultSets=True;
AttachDbFilename=|DataDirectory|ProductsContext.mdf"
providerName="System.Data.SqlClient" />
</connectionStrings>
此設定會新增 LocalDB 資料庫的連接字串。 當您在本機執行應用程式時將使用該資料庫。
接下來,將一個名為 ProductsContext
的類別新增至 Models 資料夾:
using System.Data.Entity;
namespace ProductService.Models
{
public class ProductsContext : DbContext
{
public ProductsContext()
: base("name=ProductsContext")
{
}
public DbSet<Product> Products { get; set; }
}
}
在建構函式中,"name=ProductsContext"
會提供連接字串的名稱。
設定 OData 端點
開啟檔案 App_Start/WebApiConfig.cs。 加入以下 using 陳述式:
using ProductService.Models;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
然後將以下程式碼加入 Register 方法中:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// New code:
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
}
}
這段程式碼會做兩件事:
- 建立實體資料模型 (EDM)。
- 新增路由。
EDM 是資料的抽像模型。 EDM 用於建立服務中繼資料文件。 ODataConventionModelBuilder 類別使用預設命名慣例建立 EDM。 這種方法需要的程式碼最少。 如果您想對 EDM 擁有更多控制權,可以使用 ODataModelBuilder 類別,透過明確新增屬性、索引鍵和導覽屬性來建立 EDM。
路由會告訴 Web API 如何將 HTTP 請求路由到端點。 若要建立 OData v4 路由,請呼叫 MapODataServiceRoute 擴充方法。
如果您的應用程式有多個 OData 端點,請為每個端點建立一個單獨的路由。 為每條路由指定唯一的路由名稱和首碼。
新增 OData 控制器
控制器是處理 HTTP 請求的類別。 您為 OData 服務中的每個實體集建立一個單獨的控制器。 在本教學課程中,您將為 Product
實體建立一個控制器。
在「方案總管」中,以滑鼠右鍵按一下「Controllers」資料夾並選擇「新增>類別」。 將類別命名為 ProductsController
。
注意
本教學課程的 OData v3 版本使用新增控制器 Scaffolding。 目前,OData v4 沒有 Scaffolding。
將 ProductsController.cs 中的樣板程式碼替換為以下內容。
using ProductService.Models;
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;
namespace ProductService.Controllers
{
public class ProductsController : ODataController
{
ProductsContext db = new ProductsContext();
private bool ProductExists(int key)
{
return db.Products.Any(p => p.Id == key);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
控制器使用 ProductsContext
類別透過 EF 存取資料庫。 請注意,控制器會覆寫 Dispose 方法來處置 ProductsContext。
這是控制器的起點。 接下來,我們將為所有 CRUD 操作新增方法。
查詢實體集
將以下方法加入 ProductsController
。
[EnableQuery]
public IQueryable<Product> Get()
{
return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
IQueryable<Product> result = db.Products.Where(p => p.Id == key);
return SingleResult.Create(result);
}
Get
方法的無參數版本會傳回整個 Products 集合。 具有 key 參數的 Get
方法會透過其索引鍵 (在本例中為 Id
屬性) 來尋找產品。
[EnableQuery] 屬性使用戶端能夠使用 $filter、$sort 和 $page 等查詢選項來修改查詢。 有關詳細資訊,請參閱「支援 OData 查詢選項」。
將實體新增至實體集中
若要讓用戶端能夠將新產品新增至資料庫中,請將以下方法新增至 ProductsController
。
public async Task<IHttpActionResult> Post(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Created(product);
}
更新實體
OData 支援兩種不同的實體更新語義,分別是 PATCH 和 PUT。
- PATCH 執行部分更新。 用戶端僅指定要更新的屬性。
- PUT 取代整個實體。
PUT 的缺點是用戶端必須傳送實體中所有屬性的值,包括未變更的值。 OData 規範規定 PATCH 是首選方式。
無論如何,以下是 PATCH 和 PUT 方法的程式碼:
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var entity = await db.Products.FindAsync(key);
if (entity == null)
{
return NotFound();
}
product.Patch(entity);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(entity);
}
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (key != update.Id)
{
return BadRequest();
}
db.Entry(update).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(update);
}
對於 PATCH,控制器使用 Delta<T> 類型來追蹤變更。
刪除實體
若要讓用戶端能夠從資料庫中刪除產品,請將以下方法新增至 ProductsController
。
public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
var product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
db.Products.Remove(product);
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}