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

作者:Mike Wasson

下載已完成的專案

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

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

在本教學課程中,我將逐步解說如何建立呼叫 OData 服務的用戶端應用程式。 OData 服務會公開下列實體:

  • Product
  • Supplier
  • ProductRating

顯示 O 資料服務實體及其屬性清單的圖表,其中連接箭號可顯示每個實體的關聯性或共同運作方式。

下列文章說明如何在 Web API 中實作 OData 服務。 (您不需要閱讀它們,即可瞭解本教學課程。)

產生服務 Proxy

第一個步驟是產生服務 Proxy。 服務 Proxy 是 .NET 類別,可定義用來存取 OData 服務的方法。 Proxy 會將方法呼叫轉譯成 HTTP 要求。

此圖顯示服務 Proxy 的 H T T P 要求呼叫從應用程式、透過服務 Proxy 和 O 資料服務來回執行。

從在 Visual Studio 中開啟 OData 服務專案開始。 按 CTRL+F5 在本機IIS Express中執行服務。 請注意本機位址,包括 Visual Studio 指派的埠號碼。 建立 Proxy 時,您將需要此位址。

接下來,開啟另一個 Visual Studio 實例,並建立主控台應用程式專案。 主控台應用程式將是我們的 OData 用戶端應用程式。 (您也可以將專案新增至與 service.) 相同的方案

注意

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

在 [方案總管] 中,以滑鼠右鍵按一下 [參考],然後選取 [新增服務參考]。

方案總管視窗的螢幕擷取畫面,其中顯示 [參考] 底下的功能表,以新增服務參考。

在 [ 新增服務參考] 對話方塊中,輸入 OData 服務的位址:

http://localhost:port/odata

其中 是埠號碼。

[新增服務參考] 視窗的螢幕擷取畫面,其中顯示 [U R L 位址] 欄位中的埠號碼,以及用來新增 [名稱] 空間的欄位。

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

按一下 [移至]。 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

若要依識別碼取得實體,請使用 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 Data Services) 主題。

篩選 ($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

Client-Side分頁 ($skip和$top)

對於大型實體集,用戶端可能會想要限制結果數目。 例如,用戶端一次可能會顯示 10 個專案。 這稱為 用戶端分頁。 (也有 伺服器端分頁,其中伺服器會限制 results 數目。) 若要執行用戶端分頁,請使用 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 方法。 例如,若要包含 Supplier 每個 Product 的 :

// 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 Data Services會自動建立這些強型別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 Data Services部落格上閱讀此部落格文章中的一些歷程記錄。 目前,PATCH 優先于 MERGE。 Web API Scaffolding 所建立的 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 採用類型 Int32 為 「Rating」 的參數,並傳 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();
        }
    }
}