🎃

【C#】HttpClientでAPIリクエストを送信する:StringContentとFormUrlEncodedContentの使い分け

2024/10/19に公開

概要

外部API利用にあたって、application/x-www-form-urlencodedでHttpリクエストをする機会がありました。

これまでapplication/jsonで送信するケースが多く、思考停止でStringContentを使用していたので、これを機会に学んだことをまとめます。

StringContentでの通信

Httpリクエストのボディで文字列を送りたい場合は、StringContentクラスを使います。

https://learn.microsoft.com/ja-jp/dotnet/api/system.net.http.stringcontent?view=net-8.0

text/plainでのリクエスト

StringContentは、デフォルトではContent-type: text/plainで送信されます。

TestApliClient.cs
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を指定します。

TestApliClient.cs
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での通信

https://learn.microsoft.com/ja-jp/dotnet/api/system.net.http.formurlencodedcontent?view=net-8.0

application/x-www-form-urlencodedで送信する場合は、FormUrlEncodedContentを使うのが楽です。

FormUrlEncodedContentを使うと、リクエストボディがkey=valueの形で&でつなげられた形式となり、値の部分はURLエンコードされます。また、Content-typeは自動でapplication/x-www-form-urlencodedが指定されます。

TestApliClient.cs
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