.NET クライアントから OData サービスを呼び出す (C#)

作成者: Mike Wasson

完成したプロジェクトをダウンロードする

このチュートリアルでは、C# クライアント アプリケーションから OData サービスを呼び出す方法について説明します。

チュートリアルで使用するソフトウェアのバージョン

このチュートリアルでは、OData サービスを呼び出すクライアント アプリケーションの作成について説明します。 OData サービスは、次のエンティティを公開します。

  • Product
  • Supplier
  • ProductRating

Diagram showing the O Data service entities and a list of their properties, with connecting arrows to show how each relate or work together.

次の記事では、Web API で OData サービスを実装する方法について説明します。 (ただし、このチュートリアルを理解するためにこれらを読む必要はありません。)

サービス プロキシを生成する

最初の手順では、サービス プロキシを生成します。 サービス プロキシは、OData サービスにアクセスするためのメソッドを定義する .NET クラスです。 このプロキシは、メソッド呼び出しを HTTP 要求に変換します。

Diagram showing the service proxy's H T T P request calls running back and forth from the application, through the service proxy, and to the O Data service.

最初に、Visual Studio で OData サービス プロジェクトを開きます。 CTRL キーを押しながら F5 キーを押して、IIS Express でサービスをローカルで実行します。 Visual Studio によって割り当てられるポート番号を含む、ローカル アドレスをメモします。 このアドレスは、プロキシを作成するときに必要になります。

次に、Visual Studio の別のインスタンスを開き、コンソール アプリケーション プロジェクトを作成します。 このコンソール アプリケーションが、OData クライアント アプリケーションになります。 (プロジェクトをサービスと同じソリューションに追加することもできます。)

Note

残りの手順では、コンソール プロジェクトを参照します。

ソリューション エクスプローラーで、[参照] を右クリックし、[サービス参照の追加] を選択します。

Screenshot of the solution explorer window, showing the menu under 'references' in order to add a new service reference.

[サービス参照の追加] ダイアログで、OData サービスのアドレスを入力します。

http://localhost:port/odata

ここで、ポートはポート番号です。

Screenshot of the 'add service reference' window, which shows the port number in the U R L address field and a field to add a Name space.

[名前空間] に「ProductService」と入力します。 このオプションは、プロキシ クラスの名前空間を定義します。

[Go] をクリックします。 Visual Studio は、OData メタデータ ドキュメントを読み取り、サービス内のエンティティを検出します。

Screenshot of the 'add service reference' dialog box, highlighting the container service, to show the operations running in it.

[OK] をクリックして、プロキシ クラスをプロジェクトに追加します。

Screenshot of the solution explorer dialog box, showing the menu under the 'product service client' and highlighting the option for 'Product Service'.

サービス プロキシ クラスのインスタンスを作成する

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 は新しい ProductProducts エンティティ セットに追加します。 プロキシを生成すると、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();
        }
    }
}