.NET クライアントから Web API を呼び出す (C#)

このコンテンツは、以前のバージョンの .NET 用です。 新しい開発では、ASP.NET Core を使用する必要があります。 ASP.NET Core Web API の使用の詳細については、以下を参照してください。

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

このチュートリアルでは、System.Net.Http.HttpClient. を使って、.NET アプリケーションから Web API を呼び出す方法を示します。

このチュートリアルでは、次の Web API を使うクライアント アプリを作成します。

アクション HTTP メソッド 相対 URI
ID で製品を取得する GET /api/products/id
新しい製品の作成 投稿 /api/products
製品を更新する PUT /api/products/id
製品を削除する DELETE /api/products/id

ASP.NET Web API を使ってこの API を実装する方法については、CRUD 操作をサポートする Web API の作成に関する記事を参照してください。

わかりやすくするために、このチュートリアルのクライアント アプリケーションは Windows コンソール アプリケーションです。 HttpClient は、Windows Phone および Windows ストア アプリでもサポートされています。 詳細については、「ポータブル ライブラリを使った複数のプラットフォームに対応する Web API クライアント コードの作成」を参照してください

注: ベース URL と相対 URI をハードコーディングされた値として渡す場合は、HttpClient API の使用に関する規則に注意してください。 HttpClient.BaseAddress プロパティは、末尾にスラッシュが付いているアドレス (/) に設定する必要があります。 たとえば、ハードコーディングされたリソース URI を HttpClient.GetAsync メソッドに渡すときは、先頭にスラッシュを含めないでください。 ID で Product を取得するには:

  1. client.BaseAddress = new Uri("https://localhost:5001/"); を設定します
  2. Product を要求します。 たとえば、client.GetAsync<Product>("api/products/4"); のようにします。

コンソール アプリケーションを作成する

Visual Studio で HttpClientSample という新しい Windows コンソール アプリを作成し、次のコードを貼り付けます。

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HttpClientSample
{
    public class Product
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }

    class Program
    {
        static HttpClient client = new HttpClient();

        static void ShowProduct(Product product)
        {
            Console.WriteLine($"Name: {product.Name}\tPrice: " +
                $"{product.Price}\tCategory: {product.Category}");
        }

        static async Task<Uri> CreateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PostAsJsonAsync(
                "api/products", product);
            response.EnsureSuccessStatusCode();

            // return URI of the created resource.
            return response.Headers.Location;
        }

        static async Task<Product> GetProductAsync(string path)
        {
            Product product = null;
            HttpResponseMessage response = await client.GetAsync(path);
            if (response.IsSuccessStatusCode)
            {
                product = await response.Content.ReadAsAsync<Product>();
            }
            return product;
        }

        static async Task<Product> UpdateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PutAsJsonAsync(
                $"api/products/{product.Id}", product);
            response.EnsureSuccessStatusCode();

            // Deserialize the updated product from the response body.
            product = await response.Content.ReadAsAsync<Product>();
            return product;
        }

        static async Task<HttpStatusCode> DeleteProductAsync(string id)
        {
            HttpResponseMessage response = await client.DeleteAsync(
                $"api/products/{id}");
            return response.StatusCode;
        }

        static void Main()
        {
            RunAsync().GetAwaiter().GetResult();
        }

        static async Task RunAsync()
        {
            // Update port # in the following line.
            client.BaseAddress = new Uri("http://localhost:64195/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));

            try
            {
                // Create a new product
                Product product = new Product
                {
                    Name = "Gizmo",
                    Price = 100,
                    Category = "Widgets"
                };

                var url = await CreateProductAsync(product);
                Console.WriteLine($"Created at {url}");

                // Get the product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Update the product
                Console.WriteLine("Updating price...");
                product.Price = 80;
                await UpdateProductAsync(product);

                // Get the updated product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Delete the product
                var statusCode = await DeleteProductAsync(product.Id);
                Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.ReadLine();
        }
    }
}

上記のコードが完成したクライアント アプリです。

RunAsync が実行され、完了するまでブロックされます。 ほとんどの HttpClient メソッドは、ネットワーク I/O を実行するため、非同期です。 すべての非同期タスクは RunAsync 内で実行されます。 通常、アプリはメイン スレッドをブロックしませんが、このアプリは対話を許可しません。

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

    try
    {
        // Create a new product
        Product product = new Product
        {
            Name = "Gizmo",
            Price = 100,
            Category = "Widgets"
        };

        var url = await CreateProductAsync(product);
        Console.WriteLine($"Created at {url}");

        // Get the product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Update the product
        Console.WriteLine("Updating price...");
        product.Price = 80;
        await UpdateProductAsync(product);

        // Get the updated product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Delete the product
        var statusCode = await DeleteProductAsync(product.Id);
        Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }

    Console.ReadLine();
}

Web API クライアント ライブラリをインストールする

NuGet パッケージ マネージャーを使って Web API クライアント ライブラリ パッケージをインストールします。

[ツール] メニューで、[NuGet パッケージ マネージャー]>[パッケージ マネージャー コンソール] の順に選択します。 パッケージ マネージャー コンソール (PMC) に次のコマンドを入力します。

Install-Package Microsoft.AspNet.WebApi.Client

上記のコマンドを使って、次の NuGet パッケージをプロジェクトに追加します。

  • Microsoft.AspNet.WebApi.Client
  • Newtonsoft.Json

Newtonsoft.Json (Json.NET とも呼ばれます) は、.NET 用の一般的な高パフォーマンス JSON フレームワークです。

モデル クラスを追加する

次の Product クラスを調べます。

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

このクラスは、Web API で使われるデータ モデルと一致します。 アプリで HttpClient を使い、HTTP 応答から Product インスタンスを読み取ることができます。 アプリで逆シリアル化コードを記述する必要はありません。

HttpClient の作成と初期化

静的 HttpClient プロパティを調べます。

static HttpClient client = new HttpClient();

HttpClient は一度インスタンス化されたら、アプリケーションの有効期間にわたって再利用されることを目的としています。 次の条件により、SocketException エラーが発生する可能性があります。

  • 要求ごとに新しい HttpClient インスタンスを作成する。
  • サーバーが高負荷状態。

要求ごとに新しい HttpClient インスタンスを作成すると、使用できるソケットが枯渇する可能性があります。

次のコードを使って HttpClient インスタンスを初期化します。

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

上記のコードでは次の操作が行われます。

  • HTTP 要求のベース URI を設定します。 ポート番号をサーバー アプリで使うポートに変更します。 サーバー アプリのポートが使われていないと、アプリは機能しません。
  • Accept ヘッダーを "application/json" に設定します。 このヘッダーを設定することで、サーバーに JSON 形式でデータを送信するように指示します。

GET 要求を送信してリソースを取得する

次のコードを使って、製品の GET 要求を送信します。

static async Task<Product> GetProductAsync(string path)
{
    Product product = null;
    HttpResponseMessage response = await client.GetAsync(path);
    if (response.IsSuccessStatusCode)
    {
        product = await response.Content.ReadAsAsync<Product>();
    }
    return product;
}

GetAsync メソッドを使って HTTP GET 要求を送信します。 メソッドが完了すると、HTTP 応答を含む HttpResponseMessage が返されます。 応答内の状態コードが成功コードの場合、応答本文には製品の JSON 表現が含まれます。 ReadAsAsync を呼び出して、JSON ペイロードを Product インスタンスに逆シリアル化します。 応答本文は任意の大きさになる可能性があるため、ReadAsAsync メソッドは非同期です。

HTTP 応答にエラー コードが含まれている場合でも、HttpClient は例外をスローしません。 状態がエラー コードの場合、代わりに IsSuccessStatusCode プロパティが false になります。 HTTP エラー コードを例外として扱うには、応答オブジェクトで HttpResponseMessage.EnsureSuccessStatusCode を呼び出します。 状態コードが 200 から 299 の範囲に含まれない場合、EnsureSuccessStatusCode は例外をスローします。 HttpClient は、要求がタイム アウトした場合など、他の理由で例外をスローする可能性があることに注意してください。

逆シリアル化するメディア型フォーマッタ

パラメーターを指定せずに ReadAsAsync を呼び出すと、既定のセットの "メディア フォーマッタ" を使って応答本文を読み取ります。 既定のフォーマッタは、JSON、XML、フォーム URL エンコード済みデータをサポートします。

既定のフォーマッタを使う代わりに、ReadAsAsync メソッドにフォーマッタの一覧を指定することもできます。 カスタムのメディアタイプ フォーマッタがある場合は、フォーマッタの一覧を使うと便利です。

var formatters = new List<MediaTypeFormatter>() {
    new MyCustomFormatter(),
    new JsonMediaTypeFormatter(),
    new XmlMediaTypeFormatter()
};
resp.Content.ReadAsAsync<IEnumerable<Product>>(formatters);

詳細については、「ASP.NET Web API 2 のメディア フォーマッタ」を参照してください

リソースを作成するための POST 要求の送信

次のコードを使って、JSON 形式の Product インスタンスを含む POST 要求を送信します。

static async Task<Uri> CreateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PostAsJsonAsync(
        "api/products", product);
    response.EnsureSuccessStatusCode();

    // return URI of the created resource.
    return response.Headers.Location;
}

PostAsJsonAsync メソッド:

  • オブジェクトを JSON にシリアル化します。
  • POST 要求で JSON ペイロードを送信します。

要求が成功した場合:

  • 201 (Created) 応答が返されます。
  • 応答には、作成されたリソースの URL が Location ヘッダーに含まれているはずです。

リソースを更新するための PUT 要求の送信

次のコードを使って、製品を更新する PUT 要求を送信します。

static async Task<Product> UpdateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PutAsJsonAsync(
        $"api/products/{product.Id}", product);
    response.EnsureSuccessStatusCode();

    // Deserialize the updated product from the response body.
    product = await response.Content.ReadAsAsync<Product>();
    return product;
}

PutAsJsonAsync メソッドは、POST ではなく PUT 要求を送信する点を除けば、PostAsJsonAsync と同様に機能します。

リソースを削除するための DELETE 要求の送信

次のコードを使って、製品を削除する DELETE 要求を送信します。

static async Task<HttpStatusCode> DeleteProductAsync(string id)
{
    HttpResponseMessage response = await client.DeleteAsync(
        $"api/products/{id}");
    return response.StatusCode;
}

GET と同様に、DELETE 要求には要求本文がありません。 DELETE で JSON または XML 形式を指定する必要はありません。

サンプルをテストする

クライアント アプリをテストするには:

  1. サーバー アプリをダウンロードして実行します。 サーバー アプリが機能していることを確認します。 たとえば、http://localhost:64195/api/products は製品の一覧を返すはずです。

  2. HTTP 要求のベース URI を設定します。 ポート番号をサーバー アプリで使うポートに変更します。

    static async Task RunAsync()
    {
        // Update port # in the following line.
        client.BaseAddress = new Uri("http://localhost:64195/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
    
  3. クライアント アプリを実行します。 次の出力が生成されます。

    Created at http://localhost:64195/api/products/4
    Name: Gizmo     Price: 100.0    Category: Widgets
    Updating price...
    Name: Gizmo     Price: 80.0     Category: Widgets
    Deleted (HTTP Status = 204)