共用方式為


使用 Web API 2 建立 OData v3 端點

演講者:Mike Wasson

下載已完成的專案

開放式資料協定 (OData) 是一種網路資料存取協定。 OData 提供了一種統一的方式來建構資料、查詢資料以及透過 CRUD 操作 (建立、讀取、更新和刪除) 操作資料集。 OData 支援 AtomPub (XML) 和 JSON 格式。 OData 也定義了一種公開有關資料的中繼資料的方法。 用戶端可以使用中繼資料來發現資料集的類型資訊和關係。

ASP.NET Web API 可以輕鬆地為資料集建立 OData 端點。 您可以精確控制端點支援哪些 OData 操作。 您可以裝載多個 OData 端點以及非 OData 端點。 您可以完全控制資料模型、後端業務邏輯和資料層。

教學課程中使用的軟體版本

ASP.NET 和 Web Tools 2012.2 更新中新增了 Web API OData 支援。 但是,本教學課程使用 Visual Studio 2013 中新增的 Scaffolding。

在本教學課程中,您將建立一個用戶端可以查詢的簡單 OData 端點。 您還將為端點建立一個 C# 用戶端。 完成本教學課程後,下一組教學課程將展示如何新增更多功能,包括實體關係、動作和 $expand/$select。

建立 Visual Studio 專案

在本教學課程中,您將建立一個支援基本 CRUD 操作的 OData 端點。 端點將公開單一資源,即產品清單。 後續教學課程將增加更多功能。

啟動 Visual Studio,然後從「開始」頁面選擇「新專案」。 或者,從「檔案」功能表中選擇「新增」,然後選擇「專案」。

在「範本」窗格中,選擇「已安裝的範本」並展開「Visual C#」節點。 在「Visual C#」下,選擇「Web」。 選擇 ASP.NET Web 應用程式範本。

新專案視窗的螢幕擷取畫面,顯示了範本窗格的路徑,並醒目顯示了選擇 ASP.NET Web 應用程式選項的說明。

在「新 ASP.NET 專案」對話方塊中,選擇「空白」範本。 在「新增資料夾和核心參考...」下,選取 Web API。 按一下 [確定]

ASP.NET 專案對話方塊的螢幕擷取畫面,其中顯示範本選項方塊並醒目顯示「空白」選項。

新增實體模型

「模型」是代表應用程式中資料的物件。 對於本教學課程,我們需要一個代表產品的模型。 此模型對應於我們的 OData 實體類型。

在「方案總管」中,以滑鼠右鍵按一下「模型」資料夾。 從上下文功能表中,選擇「新增」,然後選擇「類別」。

方案總管對話方塊的螢幕擷取畫面,其中顯示每個選項的功能表清單,並醒目顯示每個選項,最終引導至類別選項。

在「新增」項目對話方塊中,將類別命名為「Product」。

「新增專案」視窗的螢幕擷取畫面,顯示預設排序並顯示類別選項,以及下面空白欄位中的「product.cs」字樣。

注意

按照慣例,模型類別會放置在 Models 資料夾中。 您不必在自己的專案中遵循此慣例,但在本教學課程中我們會這麼做。

在 Product.cs 檔案中,新增以下類別定義:

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查詢產品。 此欄位也將是後端資料庫中的主鍵。

現在建置專案。 在下一步中,我們將使用一些 Visual Studio Scaffolding,透過反射來尋找產品類型。

新增 OData 控制器

控制器是處理 HTTP 請求的類別。 您可以為 OData 服務中的每個實體集定義一個單獨的控制器。 在本教學課程中,我們將建立一個控制器。

在方案總管中,以滑鼠右鍵按一下 Controllers 資料夾。 選擇「新增」,然後選擇「控制器」。

方案總管視窗的螢幕擷取畫面,醒目顯示控制器選項,然後顯示用於新增 OData 控制器的功能表。

在「新增 Scaffold」對話方塊中,選擇「具有動作的 Web API 2 OData 控制器,使用 Entity Framework」。

「新增 Scaffold」畫面的螢幕擷取畫面,顯示控制器選項功能表,並醒目顯示 Web API 2 OData 控制器。

在「新增控制器」對話方塊中,將控制器命名為「ProductsController」。 選取「使用非同步控制器動作」核取方塊。 在模型下拉式清單中,選擇「產品」類別。

新增控制器對話方塊的螢幕擷取畫面,顯示控制器名稱、模型類別下拉式清單和資料上下文類別欄位。

按一下「新資料上下文...」按鈕。 保留資料上下文類型的預設名稱,然後按一下「新增」。

新資料上下文視窗的螢幕擷取畫面,顯示「新資料上下文類型」欄位,並顯示資料上下文類型的預設名稱。

按一下「新增控制器」對話方塊中的「新增」以新增控制器。

新增控制器對話方塊的螢幕擷取畫面,顯示了不同的欄位要求,以及一個「使用非同步控制器動作」核取方塊。

請注意:如果收到錯誤訊息「取得類型時發生錯誤...」,請確保在新增「產品」類別後建置了 Visual Studio 專案。 Scaffolding 會使用反射來尋找類別。

Microsoft Visual Studio 的螢幕擷取畫面,顯示紅色圓圈「X」,後面跟著「錯誤」一詞以及錯誤的詳細訊息。

Scaffolding 為專案新增兩個程式碼檔案:

  • Products.cs 定義實作 OData 端點的 Web API 控制器。
  • ProductServiceContext.cs 提供使用 Entity Framework 查詢底層資料庫的方法。

專案視窗的螢幕擷取畫面,顯示了產品服務功能表並圈出了控制器和模型下兩個新增的檔案。

新增 EDM 和路由

在方案總管中,展開 App_Start 資料夾,並開啟名為 WebApiConfig.cs 的檔案。 此類別會保存 Web API 的設定碼。 以下列內容取代此程式碼:

using ProductService.Models;
using System.Web.Http;
using System.Web.Http.OData.Builder;

namespace ProductService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Product>("Products");
            config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

這段程式碼會做兩件事:

  • 為 OData 端點建立實體資料模型 (EDM)。
  • 新增端點的路由。

EDM 是資料的抽像模型。 EDM 用於建立中繼資料文件並定義服務的 URI。 ODataConventionModelBuilder 會使用一組預設命名慣例 EDM 建立 EDM。 這種方法需要的程式碼最少。 如果您想對 EDM 擁有更多控制權,可以使用 ODataModelBuilder 類別,透過明確新增屬性、索引鍵和導覽屬性來建立 EDM。

EntitySet 方法會將實體集新增至 EDM:

modelBuilder.EntitySet<Product>("Products");

字串「Products」定義實體集的名稱。 控制器的名稱必須與實體集的名稱相符。 在本教學課程中,實體集名為「Products」,控制器名為 ProductsController。 如果您將實體集命名為「ProductSet」,則控制器命名為 ProductSetController。 請注意,一個端點可以有多個實體集。 為每個實體集呼叫 EntitySet<T>,然後定義對應的控制器。

MapODataRoute 方法為 OData 端點新增路由。

config.Routes.MapODataRoute("ODataRoute", "odata", model);

第一個參數是路由的易記名稱。 您的服務的用戶端看不到此名稱。 第二個參數是端點的 URI 首碼。 根據此程式碼,Products 實體集的 URI 為 http://hostname/odata/Products。 您的應用程式可以有多個 OData 端點。 對於每個端點,請呼叫 MapODataRoute,並提供唯一的路由名稱和唯一的 URI 首碼。

為資料庫新增種子 (選用)

在此步驟中,您將使用 Entity Framework 為資料庫新增一些測試資料。 此步驟是選擇性的,但這可以讓您立即測試 OData 端點。

從「工具」功能表中,選擇「NuGet 套件管理員」,然後選擇「套件管理員主控台」。 在「套件管理員主控台」視窗中,輸入以下命令:

Enable-Migrations

這將會新增一個名為 Migrations 的資料夾和一個名為 Configuration.cs 的程式碼檔案。

方案總管的產品服務功能表的螢幕擷取畫面,圈出了新增的名為「migrations」的資料夾,並顯示了其中的檔案。

開啟此檔案並將以下程式碼新增至 Configuration.Seed 方法。

protected override void Seed(ProductService.Models.ProductServiceContext context)
{
    // New code 
    context.Products.AddOrUpdate(new Product[] {
        new Product() { ID = 1, Name = "Hat", Price = 15, Category = "Apparel" },
        new Product() { ID = 2, Name = "Socks", Price = 5, Category = "Apparel" },
        new Product() { ID = 3, Name = "Scarf", Price = 12, Category = "Apparel" },
        new Product() { ID = 4, Name = "Yo-yo", Price = 4.95M, Category = "Toys" },
        new Product() { ID = 5, Name = "Puzzle", Price = 8, Category = "Toys" },
    });
}

在「套件管理員主控台」視窗中,輸入以下命令:

Add-Migration Initial
Update-Database

這些命令產生建立資料庫的程式碼,然後執行該程式碼。

探索 OData 端點

在本節中,我們將使用 Fiddler Web Debugging Proxy 將請求傳送到端點,並檢查回應訊息。 這將幫助您了解 OData 端點的功能。

在 Visual Studio 中,按 F5 開始偵錯。 預設情況下,Visual Studio 會將瀏覽器開啟到 http://localhost:*port*,其中 port 是在專案設定中設定的連接埠號碼。

您可以在專案設定中變更連接埠號碼。 在「方案總管」中,以滑鼠右鍵按一下該專案並選擇「屬性」。 在屬性視窗中,選擇「Web」。 在專案 URL 下輸入連接埠號碼。

服務文件

服務文件包含 OData 端點的實體集清單。 若要取得服務文件,請向服務的根 URI 傳送 GET 請求。

使用 Fiddler,在 Composer 標籤中輸入以下 URI:http://localhost:port/odata/,其中 port 是連接埠號碼。

服務文件視窗的螢幕擷取畫面,顯示了不同的標籤,其中選擇了「已解析」標籤,並在編寫器欄位中顯示了 URL 資料。

按一下「執行」按鈕。 Fiddler 會向您的應用程式傳送 HTTP GET 請求。 您應該會在 Web 工作階段清單中看到回應。 如果一切正常,狀態代碼將為 200。

Web 工作階段清單的螢幕擷取畫面,顯示結果編號為 200 的 HTTP 協定以及 URL 位址和主機。

按兩下「Web 工作階段」清單中的回應可在「偵測器」標籤中查看回應訊息的詳細資訊。

Web 工作階段清單的偵測器標籤的螢幕擷取畫面,顯示請求標頭回應和 XML 資訊。

原始 HTTP 回應訊息應類似於以下內容:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/atomsvc+xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 17:51:01 GMT
Content-Length: 364

<?xml version="1.0" encoding="utf-8"?>
<service xml:base="http://localhost:60868/odata" 
    xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title type="text">Default</atom:title>
    <collection href="Products">
        <atom:title type="text">Products</atom:title>
    </collection>
    </workspace>
</service></pre>

預設情況下,Web API 以 AtomPub 格式傳回服務文件。 若要請求 JSON,請將下列標頭新增至 HTTP 請求中:

Accept: application/json

Web 工作階段視窗的螢幕擷取畫面,顯示了請求標頭部分中 API 的回應,並圈出了在何處寫入 json 請求。

現在 HTTP 回應包含 JSON 酬載:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 22:59:28 GMT
Content-Length: 136

{
  "odata.metadata":"http://localhost:60868/odata/$metadata","value":[
    {
      "name":"Products","url":"Products"
    }
  ]
}

服務中繼資料文件

服務中繼資料文件使用稱為概念模式定義語言 (CSDL) 的 XML 語言描述服務的資料模型。 中繼資料文件會顯示服務中資料的結構,並且可用於產生用戶端程式碼。

若要取得中繼資料文件,請將 GET 請求傳送至 http://localhost:port/odata/$metadata。 以下是本教學課程中顯示端點的中繼資料。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:05:52 GMT
Content-Length: 1086

<?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="ProductService.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityType Name="Product">
        <Key>
          <PropertyRef Name="ID" />
        </Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Price" Type="Edm.Decimal" Nullable="false" />
        <Property Name="Category" Type="Edm.String" />
      </EntityType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
        <EntitySet Name="Products" EntityType="ProductService.Models.Product" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

實體集

若要取得 Products 實體集,請將 GET 請求傳送至 http://localhost:port/odata/Products

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:01:31 GMT
Content-Length: 459

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products","value":[
    {
      "ID":1,"Name":"Hat","Price":"15.00","Category":"Apparel"
    },{
      "ID":2,"Name":"Socks","Price":"5.00","Category":"Apparel"
    },{
      "ID":3,"Name":"Scarf","Price":"12.00","Category":"Apparel"
    },{
      "ID":4,"Name":"Yo-yo","Price":"4.95","Category":"Toys"
    },{
      "ID":5,"Name":"Puzzle","Price":"8.00","Category":"Toys"
    }
  ]
}

Entity

若要取得單一產品,請向 http://localhost:port/odata/Products(1) 傳送 GET 請求,其中「1」是產品 ID。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:04:29 GMT
Content-Length: 140

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products/@Element","ID":1,
      "Name":"Hat","Price":"15.00","Category":"Apparel"
}

OData 序列化格式

OData 支援多種序列化格式:

  • Atom Pub (XML)
  • JSON「精簡」(在 OData v3 中引入)
  • JSON「詳細」(OData v2)

預設情況下,Web API 使用 AtomPubJSON「精簡」格式。

若要取得 AtomPub 格式,請將 Accept 標頭設定為「application/atom+xml」。 以下是回應本文範例:

<?xml version="1.0" encoding="utf-8"?>
<entry xml:base="http://localhost:60868/odata" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
  <id>http://localhost:60868/odata/Products(1)</id>
  <category term="ProductService.Models.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <link rel="edit" href="http://localhost:60868/odata/Products(1)" />
  <link rel="self" href="http://localhost:60868/odata/Products(1)" />
  <title />
  <updated>2013-09-23T23:42:11Z</updated>
  <author>
    <name />
  </author>
  <content type="application/xml">
    <m:properties>
      <d:ID m:type="Edm.Int32">1</d:ID>
      <d:Name>Hat</d:Name>
      <d:Price m:type="Edm.Decimal">15.00</d:Price>
      <d:Category>Apparel</d:Category>
    </m:properties>
  </content>
</entry>

您可以看到 Atom 格式有個明顯的缺點:它比 JSON 詳細得多。 但是,如果您有一個理解 AtomPub 的用戶端,則該用戶端可能更偏好使用該格式而不是 JSON。

以下是同一實體的 JSON 精簡版:

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products/@Element",
  "ID":1,
  "Name":"Hat",
  "Price":"15.00",
  "Category":"Apparel"
}

JSON 精簡格式是在 OData 協定版本 3 中引入的。 為了向後相容,用戶端可以請求較舊的「詳細」JSON 格式。 若要請求詳細 JSON,請將 Accept 標頭設為 application/json;odata=verbose。 這是詳細版本:

{
  "d":{
    "__metadata":{
      "id":"http://localhost:18285/odata/Products(1)",
      "uri":"http://localhost:18285/odata/Products(1)",
      "type":"ProductService.Models.Product"
    },"ID":1,"Name":"Hat","Price":"15.00","Category":"Apparel"
  }
}

這種格式在回應本文中會傳遞更多中繼資料,這可能會在整個工作階段中增加相當大的額外負荷。 此外,它還透過將物件包裝在名為「d」的屬性中來新增間接層級。

後續步驟