.NET クライアントから OData サービスを呼び出す (C#)
作成者: Mike Wasson
このチュートリアルでは、C# クライアント アプリケーションから OData サービスを呼び出す方法について説明します。
チュートリアルで使用するソフトウェアのバージョン
- Visual Studio 2013 (連携対象: Visual Studio 2012)
- WCF Data Services クライアント ライブラリ
- Web API 2。 (OData サービスの例は Web API 2 を使用して構築されていますが、クライアント アプリケーションは Web API に依存していません)。
このチュートリアルでは、OData サービスを呼び出すクライアント アプリケーションの作成について説明します。 OData サービスは、次のエンティティを公開します。
Product
Supplier
ProductRating
次の記事では、Web API で OData サービスを実装する方法について説明します。 (ただし、このチュートリアルを理解するためにこれらを読む必要はありません。)
サービス プロキシを生成する
最初の手順では、サービス プロキシを生成します。 サービス プロキシは、OData サービスにアクセスするためのメソッドを定義する .NET クラスです。 このプロキシは、メソッド呼び出しを HTTP 要求に変換します。
最初に、Visual Studio で OData サービス プロジェクトを開きます。 CTRL キーを押しながら F5 キーを押して、IIS Express でサービスをローカルで実行します。 Visual Studio によって割り当てられるポート番号を含む、ローカル アドレスをメモします。 このアドレスは、プロキシを作成するときに必要になります。
次に、Visual Studio の別のインスタンスを開き、コンソール アプリケーション プロジェクトを作成します。 このコンソール アプリケーションが、OData クライアント アプリケーションになります。 (プロジェクトをサービスと同じソリューションに追加することもできます。)
Note
残りの手順では、コンソール プロジェクトを参照します。
ソリューション エクスプローラーで、[参照] を右クリックし、[サービス参照の追加] を選択します。
[サービス参照の追加] ダイアログで、OData サービスのアドレスを入力します。
http://localhost:port/odata
ここで、ポートはポート番号です。
[名前空間] に「ProductService」と入力します。 このオプションは、プロキシ クラスの名前空間を定義します。
[Go] をクリックします。 Visual Studio は、OData メタデータ ドキュメントを読み取り、サービス内のエンティティを検出します。
[OK] をクリックして、プロキシ クラスをプロジェクトに追加します。
サービス プロキシ クラスのインスタンスを作成する
Main
メソッド内で、次のようにプロキシ クラスの新しいインスタンスを作成します。
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 を使用します。 プロキシを更新する必要はありません。
次のコードでは、要求 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
コレクションを列挙するときに、プロキシ クラスによって自動的に行われます。
アプリケーションを実行すると、出力は次のようになります。
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 は、データのフィルター処理、並べ替え、ページングなどに使用できるクエリ オプションを定義します。 サービス プロキシでは、さまざまな LINQ 式を使用してこれらのオプションを適用できます。
このセクションでは、簡単な例を示します。 詳細については、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'
プロキシによって 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)
大規模なエンティティ セットの場合、クライアントでは結果の数を制限する必要がある場合があります。 たとえば、1 つのクライアントには一度に 10 個のエントリが表示される場合があります。 これは、クライアント側ページングと呼ばれます。 (サーバーによって結果の数が制限される、サーバー側ページングもあります。)クライアント側ページングを実行するには、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
メソッドを使用します。 たとえば、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 を呼び出さないでください。この場合、プロキシによって拡張が自動的に含まれます。 次の例では、各製品の名前と仕入先を取得します。
// 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
エンティティ セットに追加します。 プロキシを生成すると、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);
}
}
2 つのエンティティ間のリンクを追加するには、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
コレクションに製品を追加しています。
ナビゲーション プロパティが 1 つのエンティティである場合、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 オプションは、代わりに HTTP PATCH を送信するように WCF に指示します。
Note
なぜ PATCH と MERGE を使用するのでしょうか? 元の HTTP 1.1 仕様 (RCF 2616) では、"部分的な更新" セマンティクスを持つ HTTP メソッドは定義されませんでした。 部分的な更新をサポートするために、OData 仕様では MERGE メソッドが定義されました。 2010 年に、RFC 5789 では、部分的な更新に対して PATCH メソッドを定義しました。 その履歴の一部については、WCF Data Services ブログ上のこのブログ投稿で確認できます。 現在、MERGE よりも PATCH が優先されています。 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 メタデータ ドキュメントではアクションが説明されていますが、プロキシ クラスでは、厳密に型指定されたメソッドは作成されません。 引き続きジェネリック 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();
詳細については、「サービス操作とアクションの呼び出し」を参照してください。
1 つのオプションは、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();
}
}
}