從 .NET 用戶端呼叫 OData 服務 (C#)
作者:Mike Wasson
本教學課程說明如何從 C# 用戶端應用程式呼叫 OData 服務。
教學課程中使用的軟體版本
- Visual Studio 2013 (適用于 Visual Studio 2012)
- WCF 資料服務用戶端程式庫
- Web API 2. (範例 OData 服務是使用 Web API 2 所建置,但用戶端應用程式不相依于 Web API.)
在本教學課程中,我將逐步解說如何建立呼叫 OData 服務的用戶端應用程式。 OData 服務會公開下列實體:
Product
Supplier
ProductRating
下列文章說明如何在 Web API 中實作 OData 服務。 (您不需要閱讀它們,即可瞭解本教學課程。)
產生服務 Proxy
第一個步驟是產生服務 Proxy。 服務 Proxy 是 .NET 類別,可定義用來存取 OData 服務的方法。 Proxy 會將方法呼叫轉譯成 HTTP 要求。
從在 Visual Studio 中開啟 OData 服務專案開始。 按 CTRL+F5 在本機IIS Express中執行服務。 請注意本機位址,包括 Visual Studio 指派的埠號碼。 建立 Proxy 時,您將需要此位址。
接下來,開啟另一個 Visual Studio 實例,並建立主控台應用程式專案。 主控台應用程式將是我們的 OData 用戶端應用程式。 (您也可以將專案新增至與 service.) 相同的方案
注意
其餘步驟會參考主控台專案。
在 [方案總管] 中,以滑鼠右鍵按一下 [參考],然後選取 [新增服務參考]。
在 [ 新增服務參考] 對話方塊中,輸入 OData 服務的位址:
http://localhost:port/odata
其中 埠 是埠號碼。
針對 [命名空間],輸入 「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 Skip 和 Take 方法。 下列範例會略過前 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);
}
}
若要在兩個實體之間新增連結,請使用 AddLink 和 SetLink 方法。 下列程式碼會新增供應商和新產品,然後建立它們之間的連結。
// 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();
}
}
}
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應