【C#】HttpClientでAPIリクエストを送信する:StringContentとFormUrlEncodedContentの使い分け
概要
外部API利用にあたって、application/x-www-form-urlencoded
でHttpリクエストをする機会がありました。
これまでapplication/json
で送信するケースが多く、思考停止でStringContent
を使用していたので、これを機会に学んだことをまとめます。
StringContentでの通信
Httpリクエストのボディで文字列を送りたい場合は、StringContent
クラスを使います。
text/plainでのリクエスト
StringContent
は、デフォルトではContent-type: text/plain
で送信されます。
public class TestApiClient
{
private readonly HttpClient _httpClient;
public TestApiClient()
{
_httpClient = new HttpClient
{
BaseAddress = new Uri("https://httpbin.org/")
};
}
public async Task<string> PostTextAsync(string endpoint, string textContent)
{
// POSTリクエストを作成
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
// Content-typeを指定しない(text/plainになる)
Content = new StringContent(textContent, Encoding.UTF8)
};
HttpResponseMessage response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
string result = await response.Content.ReadAsStringAsync();
return result;
}
}
// 送信するデータを準備
var jsonData = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
// 外部APIにPOSTリクエストを送信
var response = await _apiClient.PostTextAsync("post", jsonData);
送信されるリクエストは以下の通りです。
POST HTTP/1.1
Request Method: POST
Request URI: post
Content-Type: text/plain; charset=utf-8
Content-Length: 44
Request Content: {"title": "foo", "body": "bar", "userId": 1}
application/jsonでのリクエスト
Content-type: application/json
で送信するには、StringContent
の第3引数にContent-Type
を指定します。
public class TestApiClient
{
private readonly HttpClient _httpClient;
public TestApiClient()
{
_httpClient = new HttpClient
{
BaseAddress = new Uri("https://httpbin.org/")
};
}
public async Task<string> PostJsonAsync(string endpoint, string jsonContent)
{
// POSTリクエストを作成
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
// Content-typeを明示的に指定
Content = new StringContent(jsonContent, Encoding.UTF8, "application/json")
};
HttpResponseMessage response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
string result = await response.Content.ReadAsStringAsync();
return result;
}
}
POSTリクエストを実施します。
// 送信するJSONデータを準備
var jsonData = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
// 外部APIにPOSTリクエストを送信
var response = await _apiClient.PostJsonAsync("post", jsonData);
送信されるリクエストは以下の通りです。
POST HTTP/1.1
Request Method: POST
Request URI: post
Content-Type: application/json; charset=utf-8
Content-Length: 44
Request Content: {"title": "foo", "body": "bar", "userId": 1}
FormUrlEncodedContentでの通信
application/x-www-form-urlencoded
で送信する場合は、FormUrlEncodedContent
を使うのが楽です。
FormUrlEncodedContent
を使うと、リクエストボディがkey=value
の形で&
でつなげられた形式となり、値の部分はURLエンコードされます。また、Content-type
は自動でapplication/x-www-form-urlencoded
が指定されます。
public class TestApiClient
{
private readonly HttpClient _httpClient;
public TestApiClient()
{
_httpClient = new HttpClient
{
BaseAddress = new Uri("https://httpbin.org/")
};
}
public async Task<string> PostFormAsync(string endpoint, Dictionary<string, string> formData)
{
// POSTリクエストを作成
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
Content = new FormUrlEncodedContent(formData)
};
HttpResponseMessage response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
string result = await response.Content.ReadAsStringAsync();
return result;
}
}
POSTリクエストを実施します。
// 送信するフォームデータを準備
var formData = new Dictionary<string, string>
{
{ "username", "testuser" },
{ "password", "testpassword" },
{ "testparam", "テストパラメータ" },
};
var response = await _apiClient.PostFormAsync("post", formData);
送信されるリクエストは以下の通りです。
POST HTTP/1.1
Request Method: POST
Request URI: post
Content-Type: application/x-www-form-urlencoded
Content-Length: 122
Request Content: username=testuser&password=testpassword&testparam=%E3%83%86%E3%82%B9%E3%83%88%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF
前述の通り、キーと値がkey=value
の形で&
でつなげられた形式となり、値の部分はURLエンコードされています(日本語などの非ASCII文字はパーセントエンコードされる)。
StringContentでもapplication/x-www-form-urlencodedで送信することは可能
StringContent
でも、application/x-www-form-urlencoded
で送信することは可能です。ただし、FormUrlEncodedContent
とは異なり、手動でキーと値をURLエンコードし、key=value
を&
で繋げる必要があります。
// パラメータを手動でURLエンコードする
var encodedFormData = new StringBuilder();
foreach (var kvp in formData)
{
if (encodedFormData.Length > 0)
encodedFormData.Append("&");
encodedFormData.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}");
}
// POSTリクエストを作成
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
// StringContentを使って、Content-Typeを明示的に指定
Content = new StringContent(encodedFormData, Encoding.UTF8, "application/x-www-form-urlencoded");
};
手動のエンコードは手間がかかり、ミスを招きやすいため、基本的にはContent-type: application/x-www-form-urlencoded
で送信したいならFormUrlEncodedContent
を使用するのが無難だと思います。前述の通り、FormUrlEncodedContent
では、URLエンコードやContent-Type
の設定が自動で行われるため、簡単かつ安全です。
最後に
以上です。
HttpClient
を使うことはよくあるものの、定型的に使うばかりであまり深く理解できていなかったため、整理してみました。
Discussion