共用方式為


從 .NET 用戶端呼叫 OData 服務 (C#)

演講者:Mike Wasson

下載已完成的專案

本教學課程示範如何從 C# 用戶端應用程式呼叫 OData 服務。

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

在本教學課程中,我將逐步建立一個呼叫 OData 服務的用戶端應用程式。 OData 服務公開以下實體:

  • Product
  • Supplier
  • ProductRating

顯示 OData 服務實體及其屬性清單的圖表,並使用連線箭頭顯示每個實體如何相互關聯或協同工作。

以下文章介紹如何在 Web API 中實作 OData 服務。 (但是,您不需要閱讀它們來理解本教學課程。)

產生服務 Proxy

第一步是產生服務 Proxy。 服務 Proxy 程式是一個 .NET 類別,它定義了存取 OData 服務的方法。 Proxy 將方法呼叫轉換為 HTTP 請求。

該圖顯示了服務 Proxy 的 HTTP 請求呼叫,從應用程式透過服務 Proxy 到 OData 服務來回執行。

首先在 Visual Studio 中開啟 OData 服務專案。 按 CTRL+F5 在 IIS Express 中本機執行該服務。 記下本機位址,包括 Visual Studio 指派的連接埠號碼。 建立 Proxy 時您將需要此位址。

接下來,打開 Visual Studio 的另一個執行個體並建立一個主控台應用程式專案。 主控台應用程式將是我們的 OData 用戶端應用程式。 (您也可以將專案新增至與服務相同的解決方案。)

注意

其餘步驟參考主控台專案。

在「方案總管」中,以滑鼠右鍵按一下「參考」並選擇「新增服務參考」。

方案總管視窗的螢幕擷取畫面,顯示「參考」下的功能表,以便新增新的服務參考。

在「新增服務參考」對話方塊中,鍵入 OData 服務的位址:

http://localhost:port/odata

其中 port 是連接埠號碼。

「新增服務參考」視窗的螢幕擷取畫面,其中顯示了 URL 位址欄位中的連接埠號碼以及用於新增名稱空間的欄位。

命名空間,輸入「ProductService」。 此選項定義 Proxy 類別的命名空間。

按一下 [ Go]。 Visual Studio 讀取 OData 中繼資料文件以發現服務中的實體。

「新增服務參考」對話方塊的螢幕擷取畫面,醒目顯示容器服務,以顯示其中執行的操作。

按一下「確定」將 Proxy 類別新增至您的專案。

方案總管對話方塊的螢幕擷取畫面,顯示「產品服務用戶端」下的功能表並醒目顯示「產品服務」選項。

建立服務 Proxy 類別的執行個體

在您的 Main 方法中,建立 Proxy 類別的新執行個體,如下所示:

using System;
using System.Data.Services.Client;
using System.Linq;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri uri = new Uri("http://localhost:1234/odata/");
            var container = new ProductService.Container(uri);

            // ...
        }
    }
}

再次,使用執行服務的實際連接埠號碼。 當您部署服務時,您將使用即時服務的 URI。 您不需要更新 Proxy 程式。

以下程式碼新增事件處理程序,用於將請求 URI 列印到主控台視窗。 此步驟不是必需的,但查看每個查詢的 URI 很有趣。

container.SendingRequest2 += (s, e) =>
{
    Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};

查詢服務

以下程式碼從 OData 服務取得產品清單。

class Program
{
    static void DisplayProduct(ProductService.Product product)
    {
        Console.WriteLine("{0} {1} {2}", product.Name, product.Price, product.Category);
    }

    // Get an entire entity set.
    static void ListAllProducts(ProductService.Container container)
    {
        foreach (var p in container.Products)
        {
            DisplayProduct(p);
        } 
    }
  
    static void Main(string[] args)
    {
        Uri uri = new Uri("http://localhost:18285/odata/");
        var container = new ProductService.Container(uri);
        container.SendingRequest2 += (s, e) =>
        {
            Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
        };

        // Get the list of products
        ListAllProducts(container);
    }
}

請注意,您不需要編寫任何程式碼來傳送 HTTP 請求或解析回應。 當您在 foreach 迴圈中列舉 Container.Products 集合時,Proxy 類別會自動執行此操作。

執行該應用程式時,輸出應如下所示:

GET http://localhost:60868/odata/Products
Hat 15.00   Apparel
Scarf   12.00   Apparel
Socks   5.00    Apparel
Yo-yo   4.95    Toys
Puzzle  8.00    Toys

若要透過 ID 取得實體,請使用 where 子句。

// Get a single entity.
static void ListProductById(ProductService.Container container, int id)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    {
        DisplayProduct(product);
    }
}

對於本主題的其餘部分,我不會顯示整個 Main 函式,僅顯示呼叫該服務所需的程式碼。

應用程式查詢選項

OData 定義可用於篩選、排序、分頁資料等的查詢選項。 在服務 Proxy 中,您可以透過使用各種 LINQ 運算式來套用這些選項。

在本節中,我將展示一個簡短的範例。 有關更多詳細資訊,請參閱 MSDN 上的主題「LINQ 注意事項 (WCF 資料服務)」。

篩選 ($filter)

若要篩選,請使用 where 子句。 以下範例按產品類別進行篩選。

// Use the $filter option.
static void ListProductsInCategory(ProductService.Container container, string category)
{
    var products =
        from p in container.Products
        where p.Category == category
        select p;
    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

此程式碼對應以下 OData 查詢。

GET http://localhost/odata/Products()?$filter=Category eq 'apparel'

請注意,Proxy 會將 where 子句轉換為 OData $filter 運算式。

排序 ($orderby)

若要排序,請使用 orderby 子句。 以下範例按價格從最高到最低排序。

// Use the $orderby option
static void ListProductsSorted(ProductService.Container container)
{
    // Sort by price, highest to lowest.
    var products =
        from p in container.Products
        orderby p.Price descending
        select p;

    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

這是對應的 OData 請求。

GET http://localhost/odata/Products()?$orderby=Price desc

用戶端分頁 ($skip 和 $top)

對於大型實體集,用戶端可能希望限制結果的數量。 例如,用戶端可能一次顯示 10 個項目。 這稱為用戶端分頁。 (還有伺服器端分頁,伺服器限制結果的數量。) 要執行用戶端分頁,請使用 LINQ SkipTake 方法。 以下範例跳過前 40 個結果並取得接下來的 10 個結果。

// Use $skip and $top options.
static void ListProductsPaged(ProductService.Container container)
{
    var products =
        (from p in container.Products
          orderby p.Price descending
          select p).Skip(40).Take(10);

    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

這是對應的 OData 請求:

GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10

選擇 ($select) 和展開 ($expand)

若要包含相關實體,請使用 DataServiceQuery<t>.Expand 方法。 例如,為每個 Product 加入 Supplier

// Use the $expand option.
static void ListProductsAndSupplier(ProductService.Container container)
{
    var products = container.Products.Expand(p => p.Supplier);
    foreach (var p in products)
    {
        Console.WriteLine("{0}\t{1}\t{2}", p.Name, p.Price, p.Supplier.Name);
    }
}

這是對應的 OData 請求:

GET http://localhost/odata/Products()?$expand=Supplier

若要變更回應的形狀,請使用 LINQ select 子句。 以下範例僅取得每個產品的名稱,沒有其他屬性。

// Use the $select option.
static void ListProductNames(ProductService.Container container)
{

    var products = from p in container.Products select new { Name = p.Name };
    foreach (var p in products)
    {
        Console.WriteLine(p.Name);
    }
}

這是對應的 OData 請求:

GET http://localhost/odata/Products()?$select=Name

select 子句可以包含相關實體。 在這種情況下,不要呼叫 Expand;在此案例中,Proxy 會自動加入擴充功能。 以下範例取得每個產品的名稱和供應商。

// Use $expand and $select options
static void ListProductNameSupplier(ProductService.Container container)
{
    var products =
        from p in container.Products
        select new
        {
            Name = p.Name,
            Supplier = p.Supplier.Name
        };
    foreach (var p in products)
    {
        Console.WriteLine("{0}\t{1}", p.Name, p.Supplier);
    }
}

這是對應的 OData 請求。 請注意,它包含 $expand 選項。

GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name

有關 $select 和 $expand 的更多資訊,請參閱「在 Web API 2 中使用 $select、$expand 和 $value」。

新增實體

若要將新實體新增至實體集,請呼叫 AddToEntitySet,其中 EntitySet 是實體集的名稱。 例如,AddToProducts 將新的 Product 新增至 Products 實體集。 產生 Proxy 時,WCF 資料服務會自動建立這些強型別 AddTo 方法。

// Add an entity.
static void AddProduct(ProductService.Container container, ProductService.Product product)
{
    container.AddToProducts(product);
    var serviceResponse = container.SaveChanges();
    foreach (var operationResponse in serviceResponse)
    {
        Console.WriteLine(operationResponse.StatusCode);
    }
}

若要在兩個實體之間新增連結,請使用 AddLinkSetLink 方法。 以下程式碼會新增供應商和新產品,然後在它們之間建立連結。

// Add entities with links.
static void AddProductWithSupplier(ProductService.Container container, 
    ProductService.Product product, ProductService.Supplier supplier)
{
    container.AddToSuppliers(supplier);
    container.AddToProducts(product);
    container.AddLink(supplier, "Products", product);
    container.SetLink(product, "Supplier", supplier);
    var serviceResponse = container.SaveChanges();
    foreach (var operationResponse in serviceResponse)
    {
        Console.WriteLine(operationResponse.StatusCode);
    }
}

當導覽屬性是集合時,請使用 AddLink。 在此範例中,我們將產品新增至供應商的 Products 集合。

當導覽屬性是單一實體時,請使用 SetLink。 在此範例中,我們要設定產品的 Supplier 屬性。

更新/修補檔

若要更新實體,請呼叫 UpdateObject 方法。

static void UpdatePrice(ProductService.Container container, int id, decimal price)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    { 
        product.Price = price;
        container.UpdateObject(product);
        container.SaveChanges(SaveChangesOptions.PatchOnUpdate);
    }
}

當您呼叫 SaveChanges 時會執行更新。 預設情況下,WCF 會傳送 HTTP MERGE 請求。 PatchOnUpdate 選項告訴 WCF 改為傳送 HTTP PATCH。

注意

為什麼選擇 PATCH 與 MERGE? 最初的 HTTP 1.1 規格 (RCF 2616) 並沒有定義任何具有「部分更新」語意的 HTTP 方法。 為了支援部分更新,OData 規範定義了 MERGE 方法。 2010 年,RFC 5789 定義了部分更新的 PATCH 方法。 您可以在 WCF 資料服務部落格上的這篇文章中閱讀一些歷史記錄。 如今,PATCH 比 MERGE 更受青睞。 Web API 腳手架建立的 OData 控制器支援這兩種方法。

如果要取代整個實體 (PUT 語意),請指定 ReplaceOnUpdate 選項。 這會導致 WCF 傳送 HTTP PUT 請求。

container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);

刪除實體

若要刪除實體,請呼叫 DeleteObject

static void DeleteProduct(ProductService.Container container, int id)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    {
        container.DeleteObject(product);
        container.SaveChanges();
    }
}

叫用 OData 動作

在 OData 中,動作是新增伺服器端行為的一種方法,這些行為不容易定義為實體上的 CRUD 操作。

儘管 OData 中繼資料文件描述了動作,但 Proxy 類別不會為它們建立任何強類型方法。 您仍然可以使用通用 Execute 方法呼叫 OData 動作。 但是,您需要知道參數的資料類型和傳回值。

例如,RateProduct 動作採用名為「Rating」的參數,類型為 Int32,並傳回 double。 以下程式碼顯示如何叫用此動作。

int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
    actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();

有關更多資訊,請參閱「呼叫服務操作和動作」。

一種選擇是擴展 Container 類別以提供叫用該動作的強類型方法:

namespace ProductServiceClient.ProductService
{
    public partial class Container
    {
        public double RateProduct(int productID, int rating)
        {
            Uri actionUri = new Uri(this.BaseUri,
                String.Format("Products({0})/RateProduct", productID)
                );

            return this.Execute<double>(actionUri, 
                "POST", true, new BodyOperationParameter("Rating", rating)).First();
        }
    }
}