アルパカのメモ

HttpClient

はじめに

.NET Core (C#) で API を実行したい場合などは、System.Net.Http.HttpClient を使う。

HttpClient のインスタンスはアプリケーションで1つになるように定義する。 IDisposable インターフェイスを実装しているが、自分で using 句で囲んだり Dispose メソッドを実行する必要はない。

GET

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Samples
{
    public class Class1
    {
        private static readonly HttpClient client = new HttpClient();

        public static async Task Sample1(string url)
        {
            var response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();

            var responseContent = response.Content.ReadAsStringAsync();
        }
    }
}

ステータスの確認方法

HttpClient の各メソッドを実行したとき、HTTPステータスコードがエラーになっても勝手に Exception がスローされない。 下記のいずれかの方法でステータスコードをチェックする必要がある。

var response = await client.GetAsync(url);
// ステータスコードがエラーの場合、Exception がスローされる
response.EnsureSuccessStatusCode();
if (!response.IsSuccessStatusCode)
{
    // エラー時の処理
}

POST

フォームデータを送信する (application/x-www-form-urlencoded) 場合は、FormUrlEncodedContent クラスを使う。以下サンプル。

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;

namespace Samples
{
    public class Class1
    {
        private static readonly HttpClient client = new HttpClient();

        public async Task Sample1(string url)
        {
            var values = new Dictionary<string, string>
            {
                { "param1", "value1" },
                { "param2", "value2" }
            };

            HttpContent content = new FormUrlEncodedContent(values);
            var response = await client.PostAsync(url, content);

            if (!response.IsSuccessStatusCode)
            {
                // 失敗
            }
            // 成功
        }
    }
}

Multipart データを送る

ファイルアップロードがある場合は、こちらの方法を使う。

public async Task ExampleAsync(string url, List<string> filePaths)
{
    MultipartFormDataContent rootContent = new MultipartFormDataContent();

    // ファイル以外のデータをセット
    StringContent content1 = new StringContent("value1");
    content1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
    content1.Headers.ContentDisposition.Name = "name1";
    rootContent.Add(content1);

    StringContent content2 = new StringContent("value2");
    content2.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data");
    content2.Headers.ContentDisposition.Name = "name2";
    rootContent.Add(content2);

    // アップロードファイルを送信データに追加する
    var streams = new List<IDisposable>();
    try
    {
        foreach (var filePath in filePaths)
        {
            FileInfo info = new FileInfo(filePath);

            // ファイルを開く
            var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            streams.Add(stream);

            // 送信データに追加
            var streamContent = new StreamContent(stream);
            streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
            {
                Name = "file",
                FileName = info.Name
            };

            rootContent.Add(streamContent);
        }

        // 送信
        using var request = new HttpRequestMessage(HttpMethod.Post, url);
        request.Content = rootContent;

        var response = await client.SendAsync(request);
    }
    finally
    {
        streams.ForEach(x => x.Dispose());
    }
}

日本語ファイル名が文字化けする場合

大体の場合は上記サンプルでよいが、場合によってはアップロードしたファイル名が以下のようになったりする。

=?utf-8?B?xxxxx?=

これは HttpClient が仕様に従って filename を filename* にしてエンコードしてくれているが、リクエストを受け取るサーバー側がこの仕様に対応していなかったりするのが原因。 (参考:Content-Disposition - HTTP | MDN)

対策としては、一度ファイル名をバイトコードへ変換し、それをさらに文字型にする方法がある。以下がサンプルコード。

// ヘッダー値を作る
streamContent.Headers.Add("Content-Type", "application/octet-stream");
streamContent.Headers.Add("Content-Disposition", GetContentDisposition(info.Name));

private static string GetContentDisposition(string fileName)
{
    var headerValue = "form-data; name=\"file\"; filename=\"" + fileName + "\"";
    var bytes = Encoding.UTF8.GetBytes(headerValue);
    var sb = new StringBuilder();
    foreach (var b in bytes)
    {
        sb.Append((char)b);
    }
    return sb.ToString();
}

参考:c# - How to disable base64-encoded filenames in HttpClient/MultipartFormDataContent - Stack Overflow

ヘッダーを指定する

リクエストのヘッダーを指定したい場合、 GetAsync や PostAsync メソッドは使用できない。代わりに SendAsync メソッドを使用する。サンプルは下記の通り。

using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://xxx"))
{
    // body
    var content = new StringContent("aaa");
    request.Content = content;

    // header
    request.Headers.Add("name", "value");

    // 送信
    using (var response = await client.SendAsync(request))
    {
        // TODO : レスポンスを受け取る
    }
}