C# HttpClientの使い方完全ガイド|GET/POST・JSON送受信・ヘッダー設定・エラー対策まで解説

はじめに

C#でWeb APIを呼び出すときに最もよく使われるのがHttpClientです。REST APIからJSONを取得する、フォームをPOSTする、Bearerトークン付きで認証APIを呼び出す、レスポンスのステータスコードを判定する、といった処理はすべてHttpClientで実装できます。

一方で、HttpClientは「GETするだけなら簡単」ですが、実務ではつまずきやすいポイントも多いです。たとえば、usingで毎回破棄してよいのか、Content-Typeをどこに設定すればよいのか、JSONがサーバー側で受け取れない原因は何か、タイムアウトやHttpRequestExceptionをどう扱うか、といった問題です。

この記事では、C# HttpClientの基本から、GET/POST、JSON送受信、ヘッダー設定、エラー処理、実務向けの設計まで、サンプルコード付きで解説します。

1. C#のHttpClientとは?Web API通信でできること

1-1. HttpClientの役割と基本的な使いどころ

HttpClientは、C#からHTTPリクエストを送信し、HTTPレスポンスを受け取るためのクラスです。Microsoft公式ドキュメントでも、URIで識別されるリソースにHTTP要求を送信し、応答を受信するクラスとして説明されています。

主な用途は、Web APIとの通信です。たとえば、次のような処理で使います。

C#
using System.Net.Http;

HttpClient client = new HttpClient();

HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");
string body = await response.Content.ReadAsStringAsync();

Console.WriteLine(body);

このコードでは、指定したURLにGETリクエストを送り、レスポンス本文を文字列として取得しています。実務では、取得したJSONをC#のクラスに変換したり、POSTでデータを送信したり、認証ヘッダーを付けてAPIを呼び出したりします。

1-2. HttpWebRequestやWebClientとの違い

C#には、HTTP通信に使える古いAPIとしてHttpWebRequestWebClientもあります。ただし、現在の.NET開発では、基本的にHttpClientを使うのが一般的です。

HttpWebRequestは細かい制御ができますが、コードが長くなりやすく、非同期処理も扱いにくい傾向があります。WebClientは簡単なダウンロード処理には便利でしたが、柔軟なヘッダー制御やJSON API通信には向いていません。

HttpClientは、非同期通信、ヘッダー設定、JSON送受信、リクエストごとの細かな制御、ASP.NET CoreのDI連携などに対応しやすい点が特徴です。GET、POST、PUT、PATCH、DELETE、SendAsyncなど、代表的なHTTPメソッド用のAPIも用意されています。

1-3. HttpClientで実装できる主な処理一覧

HttpClientでは、次のような処理を実装できます。

処理主なメソッド・クラス
GETリクエストGetAsync, GetStringAsync, GetFromJsonAsync
POSTリクエストPostAsync, PostAsJsonAsync
PUTリクエストPutAsync, PutAsJsonAsync
DELETEリクエストDeleteAsync
任意のHTTPメソッドSendAsync, HttpRequestMessage
JSON送信StringContent, JsonSerializer, PostAsJsonAsync
JSON受信ReadAsStringAsync, ReadFromJsonAsync, GetFromJsonAsync
ヘッダー設定DefaultRequestHeaders, HttpRequestMessage.Headers
認証Authorizationヘッダー
エラー判定StatusCode, IsSuccessStatusCode, EnsureSuccessStatusCode
タイムアウトHttpClient.Timeout, CancellationToken

1-4. この記事で解決できること

この記事を読むことで、次のような疑問を解消できます。

「C# HttpClientでGETリクエストを送るにはどうすればよいか」「JSONをPOSTするにはStringContentPostAsJsonAsyncのどちらを使うべきか」「Bearerトークンをヘッダーに設定する方法は何か」「Content-Typeを設定できない原因は何か」「HttpClientusingで毎回破棄してはいけない理由は何か」「タイムアウトやHTTPエラーをどう処理すればよいか」といった、実務でよく出る疑問を順番に解説します。

2. HttpClientを使う前に知っておきたい基本

2-1. System.Net.Httpの準備と必要なusing

HttpClientを使うには、基本的に次のusingを追加します。

C#
using System.Net.Http;

JSON関連の便利な拡張メソッドを使う場合は、次も追加します。

C#
using System.Net.Http.Json;

JsonSerializerを直接使う場合は、次の名前空間も使います。

C#
using System.Text.Json;
using System.Text;

たとえば、最低限のGET処理は次のように書けます。

C#
using System.Net.Http;

HttpClient client = new HttpClient();

string result = await client.GetStringAsync("https://example.com");

Console.WriteLine(result);

コンソールアプリでawaitを使う場合、最近のC#ではトップレベルステートメントにより、そのままawaitを書けます。古い書き方をする場合は、static async Task Main()を使います。

C#
using System.Net.Http;

class Program
{
static async Task Main()
{
using HttpClient client = new HttpClient();

string result = await client.GetStringAsync("https://example.com");

Console.WriteLine(result);
}
}

ただし、後述するように、HttpClient自体をリクエストごとにusingで破棄する書き方は実務では避けるべきです。

2-2. async/awaitと非同期通信の基本

HttpClientの多くのメソッドは非同期です。たとえば、GetAsyncPostAsyncReadAsStringAsyncなどはTaskを返します。

C#
HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");
string body = await response.Content.ReadAsStringAsync();

HTTP通信はネットワークI/Oなので、同期的に待つとアプリケーションの応答性が下がる原因になります。MicrosoftのHttpClientドキュメントでも、HTTP要求はネットワークI/Oバウンドの作業であり、適切な理由がない限り非同期APIを使用することが推奨されています。

避けたい書き方は次のような同期ブロックです。

C#
// 非推奨になりやすい書き方
string result = client.GetStringAsync("https://example.com").Result;

.Result.Wait()を多用すると、ASP.NETなどの環境でデッドロックやスレッド枯渇の原因になることがあります。基本はasync/awaitで最後まで非同期にします。

C#
public async Task<string> GetPageAsync()
{
return await client.GetStringAsync("https://example.com");
}

2-3. HttpClientの基本的な書き方

基本形は次のとおりです。

C#
using System.Net.Http;

private static readonly HttpClient client = new HttpClient();

public static async Task Main()
{
HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");

if (response.IsSuccessStatusCode)
{
string body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
else
{
Console.WriteLine($"HTTP Error: {(int)response.StatusCode}");
}
}

重要なのは、HttpClientで送信し、HttpResponseMessageで結果を受け取り、response.Contentから本文を読む、という流れです。

より実務的には、usingHttpResponseMessageを破棄します。

C#
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");

string body = await response.Content.ReadAsStringAsync();

HttpClientを使い回し、HttpResponseMessageはリクエスト単位で破棄する、という考え方が基本です。

2-4. usingで毎回破棄してはいけない理由

初心者がやりがちな書き方が、次のようにリクエストのたびにHttpClientを作って破棄するコードです。

C#
public async Task<string> GetAsync()
{
using HttpClient client = new HttpClient();

return await client.GetStringAsync("https://example.com/api/users");
}

一見すると正しそうですが、実務では推奨されません。HttpClientの接続プールは内部のハンドラーに紐づいており、HttpClientを破棄すると既存の接続も破棄されます。その結果、リクエストごとに接続を作り直すことになり、パフォーマンス低下やポート枯渇の原因になります。Microsoftのガイドラインでも、要求ごとにHttpClientを作成・破棄しないこと、できるだけ再利用することが推奨されています。

避けたい書き方は次のとおりです。

C#
// 避けたい例
using var client = new HttpClient();
var response = await client.GetAsync(url);

代わりに、コンソールアプリや小規模アプリではstatic readonlyで使い回します。

C#
private static readonly HttpClient client = new HttpClient();

public async Task<string> GetAsync(string url)
{
return await client.GetStringAsync(url);
}

ASP.NET Coreでは、後述するIHttpClientFactoryを使うのが実務的です。

2-5. HttpClientを使い回すべきケースと注意点

HttpClientは、同じ設定で複数回APIを呼び出す場合に使い回すのが基本です。

C#
private static readonly HttpClient client = new HttpClient
{
BaseAddress = new Uri("https://api.example.com")
};

ただし、注意点もあります。DefaultRequestHeadersBaseAddressなど、クライアント全体に影響する設定は、基本的に最初のリクエスト送信前に設定します。リクエストごとに変わるヘッダーは、DefaultRequestHeadersではなくHttpRequestMessageに設定する方が安全です。

また、DNS変更に追従したい場合は、SocketsHttpHandlerPooledConnectionLifetimeを設定する方法があります。Microsoftのガイドラインでは、長期間使うHttpClientPooledConnectionLifetimeを設定することで、ポート枯渇とDNS変更の問題に対応できると説明されています。

C#
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15)
};

private static readonly HttpClient client = new HttpClient(handler);

ASP.NET Coreでは、IHttpClientFactoryがハンドラーの管理やログ、DIとの連携を担ってくれるため、より扱いやすくなります。

3. HttpClientでGETリクエストを送信する方法

3-1. GetAsyncでURLにアクセスする基本コード

GETリクエストを送る基本メソッドはGetAsyncです。

C#
using System.Net.Http;

private static readonly HttpClient client = new HttpClient();

public static async Task Main()
{
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");

Console.WriteLine(response.StatusCode);
}

GetAsyncHttpResponseMessageを返します。レスポンス本文だけでなく、ステータスコードやヘッダーも確認したい場合に便利です。

3-2. レスポンス本文を文字列で取得する方法

レスポンス本文はresponse.Content.ReadAsStringAsync()で取得できます。

C#
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");

string body = await response.Content.ReadAsStringAsync();

Console.WriteLine(body);

ステータスコードが成功だった場合だけ本文を読むなら、次のようにします。

C#
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");

if (response.IsSuccessStatusCode)
{
string body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
else
{
Console.WriteLine($"Request failed: {(int)response.StatusCode}");
}

単純に文字列だけ取得したい場合はGetStringAsyncも使えます。

C#
string body = await client.GetStringAsync("https://example.com/api/users");

ただし、ステータスコードを細かく確認したい場合はGetAsyncを使う方が向いています。

3-3. ステータスコードを確認する方法

ステータスコードはresponse.StatusCodeで確認できます。

C#
using System.Net;

using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

if (response.StatusCode == HttpStatusCode.OK)
{
Console.WriteLine("成功しました");
}
else if (response.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("データが見つかりません");
}
else
{
Console.WriteLine($"その他のエラー: {(int)response.StatusCode}");
}

成功かどうかだけを判定するならIsSuccessStatusCodeが便利です。

C#
if (response.IsSuccessStatusCode)
{
Console.WriteLine("2xxの成功レスポンスです");
}

2xx以外を例外として扱いたい場合はEnsureSuccessStatusCode()を使います。

C#
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");

response.EnsureSuccessStatusCode();

string body = await response.Content.ReadAsStringAsync();

EnsureSuccessStatusCodeは、成功ステータスではない場合にHttpRequestExceptionをスローします。成功と見なされる範囲は標準的な2xxです。

3-4. クエリパラメータを付けてGETする方法

クエリパラメータを付ける場合、単純なケースではURLに直接追加できます。

C#
string keyword = "csharp";
int page = 1;

string url = $"https://example.com/api/search?keyword={keyword}&page={page}";

using HttpResponseMessage response = await client.GetAsync(url);

ただし、ユーザー入力や日本語を含む場合はURLエンコードが必要です。

C#
string keyword = Uri.EscapeDataString("C# HttpClient 使い方");

string url = $"https://example.com/api/search?keyword={keyword}&page=1";

using HttpResponseMessage response = await client.GetAsync(url);

複数のパラメータを扱うなら、ASP.NET Core環境ではQueryHelpers.AddQueryStringを使う方法もあります。

C#
using Microsoft.AspNetCore.WebUtilities;

var query = new Dictionary<string, string?>
{
["keyword"] = "C# HttpClient",
["page"] = "1"
};

string url = QueryHelpers.AddQueryString("https://example.com/api/search", query);

using HttpResponseMessage response = await client.GetAsync(url);

3-5. GetFromJsonAsyncでJSONを直接取得する方法

APIがJSONを返す場合、System.Net.Http.JsonGetFromJsonAsync<T>()を使うと、レスポンスを直接クラスに変換できます。System.Net.Http.Jsonには、HttpClientHttpContent向けのJSONシリアライズ・デシリアライズ用拡張メソッドが用意されています。

C#
using System.Net.Http.Json;

public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}

User? user = await client.GetFromJsonAsync<User>("https://example.com/api/users/1");

Console.WriteLine(user?.Name);

配列やリストも取得できます。

C#
List<User>? users = await client.GetFromJsonAsync<List<User>>("https://example.com/api/users");

foreach (User user in users ?? [])
{
Console.WriteLine(user.Name);
}

GetFromJsonAsyncは簡潔ですが、エラー時のレスポンス本文を読みたい場合は、GetAsyncで受けてからReadFromJsonAsyncする方が柔軟です。

C#
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

if (response.IsSuccessStatusCode)
{
User? user = await response.Content.ReadFromJsonAsync<User>();
}
else
{
string error = await response.Content.ReadAsStringAsync();
Console.WriteLine(error);
}

4. HttpClientでPOSTリクエストを送信する方法

4-1. PostAsyncの基本的な使い方

POSTリクエストでは、送信する本文をHttpContentとして渡します。

C#
using System.Text;

string json = """
{
"name": "Taro",
"email": "taro@example.com"
}
""";

using var content = new StringContent(json, Encoding.UTF8, "application/json");

using HttpResponseMessage response = await client.PostAsync("https://example.com/api/users", content);

Console.WriteLine(response.StatusCode);

PostAsyncの第1引数は送信先URL、第2引数はリクエスト本文です。

4-2. StringContentでJSON文字列を送信する方法

JSON文字列を送る場合は、StringContentを使います。

C#
using System.Text;

string json = "{\"name\":\"Taro\",\"email\":\"taro@example.com\"}";

using var content = new StringContent(json, Encoding.UTF8, "application/json");

using HttpResponseMessage response = await client.PostAsync("https://example.com/api/users", content);

string responseBody = await response.Content.ReadAsStringAsync();

Console.WriteLine(responseBody);

ポイントは、StringContentの第3引数に"application/json"を指定することです。これにより、リクエスト本文のContent-Typeapplication/json; charset=utf-8になります。

次のようにDefaultRequestHeadersContent-Typeを設定しようとすると失敗します。

C#
// 誤り
client.DefaultRequestHeaders.Add("Content-Type", "application/json");

Content-Typeはリクエスト全体のヘッダーではなく、本文の内容を表すコンテンツヘッダーです。そのため、HttpContent.Headers.ContentType側に設定します。

C#
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

ただし、StringContentのコンストラクタで指定する方が簡単です。

4-3. PostAsJsonAsyncでオブジェクトをJSON送信する方法

オブジェクトをそのままJSONとしてPOSTしたい場合は、PostAsJsonAsyncが便利です。

C#
using System.Net.Http.Json;

public class CreateUserRequest
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}

var request = new CreateUserRequest
{
Name = "Taro",
Email = "taro@example.com"
};

using HttpResponseMessage response = await client.PostAsJsonAsync(
"https://example.com/api/users",
request
);

response.EnsureSuccessStatusCode();

PostAsJsonAsyncを使うと、JsonSerializerでのシリアライズやStringContentの作成を簡略化できます。シンプルなAPIクライアントでは非常に使いやすい方法です。

シリアライズ設定を指定したい場合は、JsonSerializerOptionsを渡します。

C#
using System.Text.Json;
using System.Net.Http.Json;

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);

using HttpResponseMessage response = await client.PostAsJsonAsync(
"https://example.com/api/users",
request,
options
);

4-4. form-dataやx-www-form-urlencodedを送信する方法

JSON以外のPOSTもHttpClientで実装できます。

application/x-www-form-urlencodedを送る場合は、FormUrlEncodedContentを使います。

C#
var values = new Dictionary<string, string>
{
["username"] = "taro",
["password"] = "password123"
};

using var content = new FormUrlEncodedContent(values);

using HttpResponseMessage response = await client.PostAsync(
"https://example.com/login",
content
);

ファイルアップロードなどで使うmultipart/form-dataは、MultipartFormDataContentを使います。

C#
using var form = new MultipartFormDataContent();

form.Add(new StringContent("Taro"), "name");

byte[] fileBytes = await File.ReadAllBytesAsync("sample.png");
var fileContent = new ByteArrayContent(fileBytes);
fileContent.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");

form.Add(fileContent, "file", "sample.png");

using HttpResponseMessage response = await client.PostAsync(
"https://example.com/upload",
form
);

API仕様で「JSON」「form-data」「x-www-form-urlencoded」のどれを要求しているかを確認し、それに合ったHttpContentを使うことが重要です。

4-5. POST結果のレスポンスを受け取る方法

POSTの結果も、GETと同じくHttpResponseMessageから取得します。

C#
using HttpResponseMessage response = await client.PostAsJsonAsync(
"https://example.com/api/users",
request
);

string responseBody = await response.Content.ReadAsStringAsync();

Console.WriteLine(response.StatusCode);
Console.WriteLine(responseBody);

レスポンスがJSONなら、ReadFromJsonAsync<T>()でクラスに変換できます。

C#
public class CreateUserResponse
{
public int Id { get; set; }
public string? Name { get; set; }
}

using HttpResponseMessage response = await client.PostAsJsonAsync(
"https://example.com/api/users",
request
);

if (response.IsSuccessStatusCode)
{
CreateUserResponse? created =
await response.Content.ReadFromJsonAsync<CreateUserResponse>();

Console.WriteLine(created?.Id);
}
else
{
string error = await response.Content.ReadAsStringAsync();
Console.WriteLine(error);
}

実務では、成功時のレスポンスだけでなく、失敗時のレスポンス本文もログに残すと調査しやすくなります。

5. HttpClientでJSONを送受信する実装パターン

5-1. System.Text.Jsonでシリアライズする方法

System.Text.Jsonを使うと、C#のオブジェクトをJSON文字列に変換できます。

C#
using System.Text.Json;
using System.Text;

var request = new
{
name = "Taro",
email = "taro@example.com"
};

string json = JsonSerializer.Serialize(request);

using var content = new StringContent(json, Encoding.UTF8, "application/json");

using HttpResponseMessage response = await client.PostAsync(
"https://example.com/api/users",
content
);

プロパティ名をWeb API向けのcamelCaseにしたい場合は、JsonSerializerOptionsを指定します。

C#
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

string json = JsonSerializer.Serialize(request, options);

PostAsJsonAsyncを使えばこの処理を簡略化できますが、ログ出力や署名計算などで送信前のJSON文字列が必要な場合は、JsonSerializer.Serializeを明示的に使うとよいでしょう。

5-2. JSONレスポンスをクラスにデシリアライズする方法

レスポンス本文を文字列で取得してから、JsonSerializer.Deserialize<T>()でクラスに変換できます。

C#
using System.Text.Json;

public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}

using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

response.EnsureSuccessStatusCode();

string json = await response.Content.ReadAsStringAsync();

User? user = JsonSerializer.Deserialize<User>(json);

Console.WriteLine(user?.Name);

APIがcamelCaseのJSONを返す場合は、PropertyNameCaseInsensitiveを有効にすると扱いやすくなります。

C#
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};

User? user = JsonSerializer.Deserialize<User>(json, options);

5-3. ReadFromJsonAsyncの使い方

ReadFromJsonAsync<T>()を使うと、レスポンス本文を直接クラスに変換できます。

C#
using System.Net.Http.Json;

using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

response.EnsureSuccessStatusCode();

User? user = await response.Content.ReadFromJsonAsync<User>();

Console.WriteLine(user?.Name);

GetFromJsonAsyncとの違いは、HttpResponseMessageを自分で扱えることです。ステータスコードやエラーレスポンスを確認したい場合は、GetAsyncReadFromJsonAsyncの組み合わせが実務向きです。

C#
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

if (response.IsSuccessStatusCode)
{
User? user = await response.Content.ReadFromJsonAsync<User>();
}
else
{
string errorBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error: {errorBody}");
}

5-4. JSONのプロパティ名が一致しない場合の対処法

JSONのプロパティ名とC#のプロパティ名が一致しない場合は、JsonPropertyName属性を使います。

C#
using System.Text.Json.Serialization;

public class User
{
[JsonPropertyName("user_id")]
public int UserId { get; set; }

[JsonPropertyName("user_name")]
public string? UserName { get; set; }
}

たとえば、APIが次のJSONを返す場合、

JSON
{
"user_id": 1,
"user_name": "Taro"
}

C#側では次のように扱えます。

C#
User? user = await response.Content.ReadFromJsonAsync<User>();

Console.WriteLine(user?.UserName);

API仕様によっては、snake_case、camelCase、PascalCaseが混在することがあります。C#のクラス設計とJSONのプロパティ名を無理に合わせるより、JsonPropertyNameで明示的に対応する方が保守しやすいです。

5-5. 日本語や文字化けを防ぐエンコーディング設定

JSONを送信するときは、UTF-8を指定するのが基本です。

C#
using System.Text;

string json = """
{
"message": "こんにちは"
}
""";

using var content = new StringContent(json, Encoding.UTF8, "application/json");

この場合、Content-Typeは概ね次のようになります。

http
Content-Type: application/json; charset=utf-8

レスポンスの文字化けが起きる場合は、サーバー側のContent-Typecharsetが正しいかも確認してください。JSON APIではUTF-8が一般的ですが、古いシステムではShift_JISなどが返ってくることがあります。

文字コードを明示して読みたい場合は、バイト配列として受け取ってから変換します。

C#
byte[] bytes = await response.Content.ReadAsByteArrayAsync();

string text = Encoding.UTF8.GetString(bytes);

Shift_JISを扱う場合は、環境によってエンコーディングプロバイダーの登録が必要です。

C#
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

Encoding sjis = Encoding.GetEncoding("shift_jis");
string text = sjis.GetString(bytes);

ただし、Web APIを新規に設計できるなら、JSONはUTF-8で統一するのが望ましいです。

6. HttpClientでヘッダーを設定する方法

6-1. DefaultRequestHeadersで共通ヘッダーを設定する

すべてのリクエストに共通で付けたいヘッダーは、DefaultRequestHeadersに設定します。

C#
using System.Net.Http.Headers;

private static readonly HttpClient client = new HttpClient();

client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);

client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");

DefaultRequestHeadersは、そのHttpClientインスタンスから送信されるリクエスト全体に適用されます。API全体で共通のAcceptUser-Agentを設定する場合に便利です。

ただし、ユーザーごと・リクエストごとに変わる値をDefaultRequestHeadersに入れると、別のリクエストに影響する可能性があります。認証トークンが頻繁に変わる場合は、次のHttpRequestMessageを使う方法が安全です。

6-2. HttpRequestMessageでリクエストごとにヘッダーを設定する

リクエスト単位でヘッダーを変えたい場合は、HttpRequestMessageを使います。

C#
using System.Net.Http.Headers;

using var request = new HttpRequestMessage(
HttpMethod.Get,
"https://example.com/api/users"
);

request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);

request.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());

using HttpResponseMessage response = await client.SendAsync(request);

string body = await response.Content.ReadAsStringAsync();

SendAsyncを使うと、GETやPOSTに限らず、任意のメソッド・ヘッダー・本文を細かく制御できます。

POSTでリクエストごとにヘッダーを付ける場合は、次のように書きます。

C#
using System.Text;
using System.Net.Http.Headers;

string json = "{\"name\":\"Taro\"}";

using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://example.com/api/users"
);

request.Content = new StringContent(json, Encoding.UTF8, "application/json");
request.Headers.Add("X-Trace-Id", Guid.NewGuid().ToString());

using HttpResponseMessage response = await client.SendAsync(request);

6-3. Content-TypeとAcceptヘッダーの違い

Content-TypeAcceptは混同しやすいヘッダーです。

Content-Typeは「送信する本文の形式」を表します。POSTやPUTでJSONを送るなら、application/jsonを設定します。

C#
using var content = new StringContent(json, Encoding.UTF8, "application/json");

Acceptは「受け取りたいレスポンスの形式」を表します。JSONで返してほしい場合は、リクエストヘッダーにapplication/jsonを設定します。

C#
using System.Net.Http.Headers;

client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);

つまり、POSTでJSONを送り、JSONで受け取りたい場合は、両方を意識します。

C#
using var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com/api/users");

request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);

request.Content = new StringContent(json, Encoding.UTF8, "application/json");

using HttpResponseMessage response = await client.SendAsync(request);

Content-Typerequest.Content.Headers側、Acceptrequest.Headers側に設定する点が重要です。

6-4. AuthorizationヘッダーでBearerトークンを設定する

Bearerトークンを使うAPIでは、Authorizationヘッダーを設定します。

C#
using System.Net.Http.Headers;

string token = "your_access_token";

using var request = new HttpRequestMessage(
HttpMethod.Get,
"https://example.com/api/me"
);

request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);

using HttpResponseMessage response = await client.SendAsync(request);

string body = await response.Content.ReadAsStringAsync();

すべてのリクエストで同じトークンを使うなら、DefaultRequestHeadersに設定できます。

C#
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);

ただし、ユーザーごとにトークンが異なるWebアプリでは、DefaultRequestHeadersに共有状態として入れると危険です。リクエストごとにHttpRequestMessageへ設定する方が安全です。

6-5. カスタムヘッダーを追加する方法

独自ヘッダーはHeaders.Addで追加できます。

C#
using var request = new HttpRequestMessage(
HttpMethod.Get,
"https://example.com/api/data"
);

request.Headers.Add("X-Api-Key", "your_api_key");
request.Headers.Add("X-Client-Version", "1.0.0");

using HttpResponseMessage response = await client.SendAsync(request);

ヘッダー名や値がHTTP仕様上不正な場合は例外が発生することがあります。その場合は、TryAddWithoutValidationを使う方法もあります。

C#
request.Headers.TryAddWithoutValidation("X-Custom-Header", "some value");

ただし、基本的にはAPI仕様に従った正しいヘッダー名と値を設定するべきです。

7. HttpClientのエラー処理と例外対策

7-1. EnsureSuccessStatusCodeの使い方

EnsureSuccessStatusCode()は、レスポンスが成功でない場合に例外を投げるメソッドです。

C#
try
{
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

response.EnsureSuccessStatusCode();

string body = await response.Content.ReadAsStringAsync();

Console.WriteLine(body);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP request failed: {ex.Message}");
}

EnsureSuccessStatusCodeは、ステータスコードが2xxの範囲外の場合にHttpRequestExceptionをスローします。

簡単な処理では便利ですが、400や404のレスポンス本文を読みたい場合は、先に本文を読むか、IsSuccessStatusCodeで分岐する方が実務的です。

C#
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

string body = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Error: {(int)response.StatusCode}");
Console.WriteLine(body);
return;
}

Console.WriteLine(body);

7-2. 400・401・403・404・500エラーの扱い方

よく使うHTTPステータスコードは、意味を理解しておくとトラブルシューティングがしやすくなります。

ステータス意味よくある原因
400 Bad Requestリクエスト不正JSON形式ミス、必須項目不足
401 Unauthorized未認証トークンなし、期限切れ
403 Forbidden権限なし権限不足、アクセス禁止
404 Not Found見つからないURLミス、ID不正
500 Internal Server ErrorサーバーエラーAPI側の例外、障害

コードでは次のように分岐できます。

C#
using System.Net;

using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users/1");

switch (response.StatusCode)
{
case HttpStatusCode.OK:
Console.WriteLine("成功");
break;

case HttpStatusCode.BadRequest:
Console.WriteLine("リクエスト内容が不正です");
break;

case HttpStatusCode.Unauthorized:
Console.WriteLine("認証が必要です");
break;

case HttpStatusCode.Forbidden:
Console.WriteLine("権限がありません");
break;

case HttpStatusCode.NotFound:
Console.WriteLine("データが見つかりません");
break;

case HttpStatusCode.InternalServerError:
Console.WriteLine("サーバーエラーです");
break;

default:
Console.WriteLine($"その他のステータス: {(int)response.StatusCode}");
break;
}

7-3. HttpRequestExceptionが発生する原因と対処法

HttpRequestExceptionは、HTTPリクエスト中に発生する代表的な例外です。

発生原因には、次のようなものがあります。

  • DNS解決に失敗した

  • 接続先サーバーに到達できない

  • SSL/TLSで失敗した

  • EnsureSuccessStatusCode()により非2xxレスポンスが例外化された

  • ネットワークが切断された

基本的な例外処理は次のとおりです。

C#
try
{
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/users");

response.EnsureSuccessStatusCode();

string body = await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP request error: {ex.Message}");
}

.NETのバージョンによっては、HttpRequestExceptionからステータスコードを取得できる場合もあります。

C#
catch (HttpRequestException ex)
{
Console.WriteLine($"Message: {ex.Message}");

if (ex.StatusCode is not null)
{
Console.WriteLine($"StatusCode: {(int)ex.StatusCode}");
}
}

ただし、エラーレスポンス本文を確実に読みたいなら、EnsureSuccessStatusCode()だけに頼らず、IsSuccessStatusCodeで分岐する設計がおすすめです。

7-4. タイムアウト時のTaskCanceledException対策

HttpClient.Timeoutを超えると、タイムアウトとして例外が発生します。タイムアウト時はTaskCanceledExceptionOperationCanceledExceptionとして扱われることがあります。

C#
client.Timeout = TimeSpan.FromSeconds(10);

try
{
using HttpResponseMessage response = await client.GetAsync("https://example.com/api/slow");
}
catch (TaskCanceledException ex)
{
Console.WriteLine("タイムアウトした可能性があります");
Console.WriteLine(ex.Message);
}

より明確に制御したい場合は、CancellationTokenSourceを使います。

C#
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

try
{
using HttpResponseMessage response = await client.GetAsync(
"https://example.com/api/slow",
cts.Token
);
}
catch (OperationCanceledException)
{
Console.WriteLine("リクエストがキャンセルまたはタイムアウトしました");
}

HttpClient.Timeoutはクライアント全体に適用されるため、リクエストごとにタイムアウトを変えたい場合はCancellationTokenSourceを使う方が柔軟です。

7-5. レスポンス本文からエラーメッセージを取得する方法

APIによっては、400や500のときにJSON形式でエラー詳細を返します。

JSON
{
"message": "Name is required"
}

このようなエラー本文を取得するには、失敗時にもReadAsStringAsync()を呼び出します。

C#
using HttpResponseMessage response = await client.PostAsJsonAsync(
"https://example.com/api/users",
request
);

string body = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"StatusCode: {(int)response.StatusCode}");
Console.WriteLine($"ErrorBody: {body}");
return;
}

Console.WriteLine($"Success: {body}");

エラーレスポンスをクラスに変換することもできます。

C#
public class ErrorResponse
{
public string? Message { get; set; }
}

if (!response.IsSuccessStatusCode)
{
ErrorResponse? error = await response.Content.ReadFromJsonAsync<ErrorResponse>();

Console.WriteLine(error?.Message);
}

ただし、エラー時に必ずJSONが返るとは限りません。HTMLや空文字が返ることもあるため、ログには文字列として残す実装も有効です。

8. HttpClientでよくあるトラブルと解決策

8-1. POSTしたJSONがサーバーで受け取れない

POSTしたJSONがサーバー側で受け取れない場合、原因として多いのは次の3つです。

1つ目は、Content-Typeapplication/jsonになっていないことです。

C#
using var content = new StringContent(json, Encoding.UTF8, "application/json");

2つ目は、JSONのプロパティ名がAPI仕様と合っていないことです。

C#
public class CreateUserRequest
{
[JsonPropertyName("user_name")]
public string UserName { get; set; } = "";
}

3つ目は、そもそもAPIがJSONではなくform-datax-www-form-urlencodedを要求していることです。API仕様書でリクエスト形式を確認してください。

悪い例は次のとおりです。

C#
// Content-Typeが設定されないため、APIによっては受け取れない
using var content = new StringContent(json);

良い例は次のとおりです。

C#
using var content = new StringContent(json, Encoding.UTF8, "application/json");

8-2. Content-Typeヘッダーを設定できない

Content-Typeを次のように設定しようとしてエラーになるケースがあります。

C#
client.DefaultRequestHeaders.Add("Content-Type", "application/json");

これは、Content-Typeがリクエスト本文に関するヘッダーだからです。DefaultRequestHeadersではなく、HttpContent側に設定します。

C#
using var content = new StringContent(json, Encoding.UTF8, "application/json");

または、明示的に設定します。

C#
content.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

AcceptContent-Typeの違いを整理すると、Acceptは「受け取りたい形式」、Content-Typeは「送る本文の形式」です。

8-3. SSL証明書エラーが発生する

SSL証明書エラーは、証明書の期限切れ、自己署名証明書、ホスト名不一致、信頼されていない認証局などで発生します。

開発環境では、検証を無効化するサンプルを見かけることがあります。

C#
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};

using var client = new HttpClient(handler);

ただし、これは本番環境では使うべきではありません。通信相手の正当性を検証できなくなり、セキュリティ上危険です。

本番では、証明書を正しく発行・更新し、クライアントが信頼できる状態にするのが正しい対処です。ローカル開発で自己署名証明書を使う場合も、開発用証明書を信頼済みに登録する方法を検討してください。

8-4. 文字化けが発生する

文字化けの原因は、多くの場合、送信時または受信時の文字コード不一致です。

送信時は、UTF-8を明示します。

C#
using var content = new StringContent(json, Encoding.UTF8, "application/json");

受信時は、サーバーのContent-Typecharsetを確認します。

C#
Console.WriteLine(response.Content.Headers.ContentType);

サーバーが正しくcharset=utf-8を返していれば、通常はReadAsStringAsyncで問題ありません。古いAPIでShift_JISなどが返る場合は、バイト配列として読み取り、適切なエンコーディングで変換します。

C#
byte[] bytes = await response.Content.ReadAsByteArrayAsync();

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
string text = Encoding.GetEncoding("shift_jis").GetString(bytes);

8-5. SocketExceptionやポート枯渇を防ぐ方法

高頻度でリクエストを送るアプリで、HttpClientを毎回作成・破棄すると、ソケットやポートが枯渇することがあります。原因は、破棄後もTCP接続がすぐには完全に解放されず、TIME-WAIT状態が残るためです。Microsoftのガイドラインでも、要求レートが高い場合に使用可能なポートが枯渇する可能性があり、回避策としてHttpClientインスタンスの再利用が推奨されています。

避けるべきコードです。

C#
for (int i = 0; i < 1000; i++)
{
using var client = new HttpClient();
await client.GetAsync("https://example.com/api/data");
}

改善例です。

C#
private static readonly HttpClient client = new HttpClient();

for (int i = 0; i < 1000; i++)
{
await client.GetAsync("https://example.com/api/data");
}

ASP.NET Coreでは、IHttpClientFactoryを使うことで、ハンドラーの再利用やライフサイクル管理を任せられます。

9. 実務で使いやすいHttpClientの設計

9-1. HttpClientをstaticで使い回す方法

コンソールアプリや小規模なバッチ処理では、static readonlyHttpClientを使い回す方法がシンプルです。

C#
public class ApiService
{
private static readonly HttpClient client = new HttpClient
{
BaseAddress = new Uri("https://example.com")
};

public async Task<string> GetUsersAsync()
{
return await client.GetStringAsync("/api/users");
}
}

DNS変更に配慮するなら、SocketsHttpHandlerPooledConnectionLifetimeを設定します。

C#
public class ApiService
{
private static readonly HttpClient client = new HttpClient(
new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15)
})
{
BaseAddress = new Uri("https://example.com")
};

public async Task<string> GetUsersAsync()
{
return await client.GetStringAsync("/api/users");
}
}

staticで使い回す場合、リクエストごとに変わるヘッダーをDefaultRequestHeadersに入れないよう注意してください。

9-2. ASP.NET CoreでIHttpClientFactoryを使う方法

ASP.NET Coreでは、IHttpClientFactoryを使う設計がよく使われます。IHttpClientFactoryは、DI、ログ、構成、ハンドラーのライフサイクル管理などと連携してHttpClientを作成できます。Microsoftのドキュメントでも、AddHttpClientによりIHttpClientFactoryと関連サービスを登録でき、名前付きクライアントや型付きクライアントなど複数の利用パターンが紹介されています。

登録例です。

C#
builder.Services.AddHttpClient();

利用例です。

C#
public class UserService
{
private readonly IHttpClientFactory httpClientFactory;

public UserService(IHttpClientFactory httpClientFactory)
{
this.httpClientFactory = httpClientFactory;
}

public async Task<string> GetUsersAsync()
{
HttpClient client = httpClientFactory.CreateClient();

return await client.GetStringAsync("https://example.com/api/users");
}
}

名前付きクライアントを使うと、APIごとに設定を分けられます。

C#
builder.Services.AddHttpClient("ExampleApi", client =>
{
client.BaseAddress = new Uri("https://example.com");
client.Timeout = TimeSpan.FromSeconds(10);
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
});

利用側です。

C#
HttpClient client = httpClientFactory.CreateClient("ExampleApi");

string result = await client.GetStringAsync("/api/users");

9-3. BaseAddressを設定してAPIクライアント化する方法

BaseAddressを設定すると、相対パスでAPIを呼び出せます。

C#
private static readonly HttpClient client = new HttpClient
{
BaseAddress = new Uri("https://example.com")
};

using HttpResponseMessage response = await client.GetAsync("/api/users");

APIクライアントクラスに整理すると、呼び出し側がURLやHTTP処理を意識しなくて済みます。

C#
using System.Net.Http.Json;

public class ExampleApiClient
{
private readonly HttpClient client;

public ExampleApiClient(HttpClient client)
{
this.client = client;
}

public async Task<List<User>> GetUsersAsync()
{
List<User>? users = await client.GetFromJsonAsync<List<User>>("/api/users");

return users ?? [];
}
}

ASP.NET Coreの型付きクライアントとして登録できます。

C#
builder.Services.AddHttpClient<ExampleApiClient>(client =>
{
client.BaseAddress = new Uri("https://example.com");
});

利用側では、ExampleApiClientをDIで受け取るだけです。

C#
public class UserController
{
private readonly ExampleApiClient apiClient;

public UserController(ExampleApiClient apiClient)
{
this.apiClient = apiClient;
}
}

9-4. タイムアウトを適切に設定する方法

HttpClient.Timeoutで全体のタイムアウトを設定できます。

C#
HttpClient client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};

IHttpClientFactoryを使う場合は、登録時に設定します。

C#
builder.Services.AddHttpClient("ExampleApi", client =>
{
client.BaseAddress = new Uri("https://example.com");
client.Timeout = TimeSpan.FromSeconds(10);
});

リクエストごとにタイムアウトを変える場合は、CancellationTokenSourceを使います。

C#
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

using HttpResponseMessage response = await client.GetAsync("/api/users", cts.Token);

タイムアウト値は短すぎても長すぎても問題です。ユーザー操作に紐づくAPIは数秒から十数秒、バッチ処理や外部連携は要件に応じて長めにするなど、用途ごとに決めるのが実務的です。

9-5. リトライ処理を実装する考え方

外部APIでは、一時的なネットワーク障害や500系エラーが発生することがあります。そのため、リトライ処理を検討することがあります。

ただし、すべてのリクエストをリトライすればよいわけではありません。GETのような冪等な処理はリトライしやすいですが、POSTで注文作成や決済処理を行う場合、重複実行の危険があります。

簡易的なリトライ例です。

C#
public async Task<HttpResponseMessage> GetWithRetryAsync(string url)
{
int maxRetries = 3;

for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
HttpResponseMessage response = await client.GetAsync(url);

if ((int)response.StatusCode >= 500 && attempt < maxRetries)
{
await Task.Delay(TimeSpan.FromSeconds(attempt));
continue;
}

return response;
}
catch (HttpRequestException) when (attempt < maxRetries)
{
await Task.Delay(TimeSpan.FromSeconds(attempt));
}
}

throw new InvalidOperationException("リトライ上限に達しました");
}

実務では、指数バックオフ、ジッター、リトライ対象ステータス、タイムアウト、サーキットブレーカーなどを設計します。.NETではIHttpClientFactoryと組み合わせた回復性パイプラインを使う設計もあります。Microsoftのガイドラインでも、リトライを含む回復性処理の例が紹介されています。

10. HttpClientの実践サンプルコード

10-1. GETでAPIからJSONを取得するサンプル

C#
using System.Net.Http.Json;

public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}

public class Sample
{
private static readonly HttpClient client = new HttpClient
{
BaseAddress = new Uri("https://example.com")
};

public static async Task Main()
{
using HttpResponseMessage response = await client.GetAsync("/api/users/1");

if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error: {(int)response.StatusCode}");
Console.WriteLine(error);
return;
}

User? user = await response.Content.ReadFromJsonAsync<User>();

Console.WriteLine(user?.Name);
}
}

この例では、ステータスコードを確認してからJSONをクラスに変換しています。エラー時の本文も読めるため、実務で扱いやすいパターンです。

10-2. POSTでJSONを送信するサンプル

C#
using System.Net.Http.Json;

public class CreateUserRequest
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}

public class CreateUserResponse
{
public int Id { get; set; }
public string? Name { get; set; }
}

public class Sample
{
private static readonly HttpClient client = new HttpClient
{
BaseAddress = new Uri("https://example.com")
};

public static async Task Main()
{
var request = new CreateUserRequest
{
Name = "Taro",
Email = "taro@example.com"
};

using HttpResponseMessage response = await client.PostAsJsonAsync(
"/api/users",
request
);

string body = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Error: {(int)response.StatusCode}");
Console.WriteLine(body);
return;
}

CreateUserResponse? created =
await response.Content.ReadFromJsonAsync<CreateUserResponse>();

Console.WriteLine($"Created user id: {created?.Id}");
}
}

PostAsJsonAsyncを使うことで、オブジェクトを直接JSONとして送信できます。

10-3. Bearer認証付きAPIを呼び出すサンプル

C#
using System.Net.Http.Headers;

public class Sample
{
private static readonly HttpClient client = new HttpClient
{
BaseAddress = new Uri("https://example.com")
};

public static async Task Main()
{
string accessToken = "your_access_token";

using var request = new HttpRequestMessage(
HttpMethod.Get,
"/api/me"
);

request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);

request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);

using HttpResponseMessage response = await client.SendAsync(request);

string body = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Error: {(int)response.StatusCode}");
Console.WriteLine(body);
return;
}

Console.WriteLine(body);
}
}

リクエストごとにBearerトークンを設定することで、複数ユーザーやトークン更新にも対応しやすくなります。

10-4. エラー時のレスポンスをログ出力するサンプル

C#
public async Task<string?> GetAsync(string url)
{
try
{
using HttpResponseMessage response = await client.GetAsync(url);

string body = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
Console.WriteLine("HTTP request failed");
Console.WriteLine($"StatusCode: {(int)response.StatusCode}");
Console.WriteLine($"ReasonPhrase: {response.ReasonPhrase}");
Console.WriteLine($"Body: {body}");

return null;
}

return body;
}
catch (TaskCanceledException ex)
{
Console.WriteLine("Request timeout or canceled");
Console.WriteLine(ex.Message);
return null;
}
catch (HttpRequestException ex)
{
Console.WriteLine("HTTP request exception");
Console.WriteLine(ex.Message);
return null;
}
}

ログ出力では、ステータスコード、理由句、レスポンス本文、例外メッセージを残すと原因調査がしやすくなります。ただし、アクセストークンや個人情報などの機密情報をログに出さないよう注意してください。

10-5. APIクライアントクラスとして整理したサンプル

C#
using System.Net.Http.Json;
using System.Net.Http.Headers;

public class UserApiClient
{
private readonly HttpClient client;

public UserApiClient(HttpClient client)
{
this.client = client;
}

public async Task<User?> GetUserAsync(int id, string accessToken)
{
using var request = new HttpRequestMessage(
HttpMethod.Get,
$"/api/users/{id}"
);

request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);

using HttpResponseMessage response = await client.SendAsync(request);

if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error: {(int)response.StatusCode} {error}");
return null;
}

return await response.Content.ReadFromJsonAsync<User>();
}

public async Task<User?> CreateUserAsync(CreateUserRequest requestBody)
{
using HttpResponseMessage response = await client.PostAsJsonAsync(
"/api/users",
requestBody
);

if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error: {(int)response.StatusCode} {error}");
return null;
}

return await response.Content.ReadFromJsonAsync<User>();
}
}

public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}

public class CreateUserRequest
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}

ASP.NET Coreで登録する場合は次のようにします。

C#
builder.Services.AddHttpClient<UserApiClient>(client =>
{
client.BaseAddress = new Uri("https://example.com");
client.Timeout = TimeSpan.FromSeconds(10);
});

このようにAPI呼び出しをクラスに閉じ込めると、コントローラーやサービス層のコードがすっきりします。

11. C# HttpClientのベストプラクティス

11-1. HttpClientを適切に再利用する

C# HttpClientの最重要ポイントは、リクエストごとにHttpClientを作成・破棄しないことです。

小規模アプリではstatic readonlyで再利用します。

C#
private static readonly HttpClient client = new HttpClient();

ASP.NET CoreではIHttpClientFactoryを使います。

C#
builder.Services.AddHttpClient();

同じAPIに対して大量のリクエストを送る場合、HttpClientの再利用はパフォーマンスと安定性に大きく影響します。

11-2. レスポンスの成功・失敗を必ず判定する

HTTP通信では、リクエストが送信できても、APIの処理が成功したとは限りません。必ずステータスコードを確認します。

C#
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
Console.WriteLine(error);
return;
}

単純な処理ならEnsureSuccessStatusCode()も便利です。

C#
response.EnsureSuccessStatusCode();

ただし、エラー本文をログに残したい場合は、IsSuccessStatusCodeで分岐する方が柔軟です。

11-3. JSON処理は型安全に実装する

JSONを文字列のまま扱うと、プロパティ名の間違いや型の不一致に気づきにくくなります。可能な限りC#のクラスを定義して型安全に扱いましょう。

C#
public class User
{
public int Id { get; set; }
public string? Name { get; set; }
}

取得時です。

C#
User? user = await response.Content.ReadFromJsonAsync<User>();

送信時です。

C#
await client.PostAsJsonAsync("/api/users", request);

API仕様が変わった場合も、クラスを見れば影響範囲を把握しやすくなります。

11-4. ヘッダー設定は用途別に使い分ける

共通ヘッダーはDefaultRequestHeadersに設定します。

C#
client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");

リクエストごとに変わるヘッダーはHttpRequestMessageに設定します。

C#
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);

Content-TypeHttpContent側に設定します。

C#
using var content = new StringContent(json, Encoding.UTF8, "application/json");

この使い分けを理解しておくと、Content-Typeを設定できない、認証ヘッダーが別リクエストに混ざる、といったトラブルを防げます。

11-5. タイムアウト・ログ・例外処理を入れる

実務のHTTP通信では、正常系だけでなく異常系を前提に設計します。

C#
try
{
using HttpResponseMessage response = await client.GetAsync(url);

string body = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"StatusCode: {(int)response.StatusCode}");
Console.WriteLine(body);
return;
}

Console.WriteLine(body);
}
catch (TaskCanceledException)
{
Console.WriteLine("タイムアウトしました");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"通信エラー: {ex.Message}");
}

最低限、次の点を入れておくと運用しやすくなります。

  • タイムアウト設定

  • ステータスコードのログ

  • エラーレスポンス本文のログ

  • HttpRequestExceptionの捕捉

  • タイムアウト時の例外処理

  • 必要に応じたリトライ

まとめ

C# HttpClientは、Web API通信を実装するための標準的なクラスです。GETリクエスト、POSTリクエスト、JSON送受信、ヘッダー設定、Bearer認証、エラー処理まで幅広く対応できます。

基本の流れは、HttpClientでリクエストを送信し、HttpResponseMessageでステータスコードを確認し、Contentからレスポンス本文を取得することです。JSON APIでは、System.Net.Http.JsonGetFromJsonAsyncPostAsJsonAsyncReadFromJsonAsyncを使うと、コードを簡潔に書けます。

一方で、HttpClientを毎回usingで破棄しない、Content-TypeHttpContentに設定する、エラーレスポンス本文をログに残す、タイムアウトを設定する、といった実務上の注意点も重要です。

小規模アプリではstatic readonly HttpClientを再利用し、ASP.NET CoreではIHttpClientFactoryを使う設計がおすすめです。API呼び出しを専用クライアントクラスに整理すれば、保守性が高く、テストしやすいコードになります。