C#でHTTP POSTに複数コンテンツを含める

# この記事で解説すること

C#での複数コンテンツのPost方法を実例付きで解説する。
（複数コンテンツというのは、例えば、画像データとその名前の2つなどの事です。）

# 複数コンテンツのHTTP POSTの実現方法

以下を使うと複数コンテンツのPOSTが行えます。

- [System.Net.Http.HttpClient](https://www.google.com/search?q=httpclient+c%23&oq=httpclient+&aqs=chrome.2.69i57j69i59j35i39j0l2j69i60l2j69i61.2714j0j7&sourceid=chrome&ie=UTF-8)のPostAsync(Uri, HttpContent)メソッド
    - あるいは、PostAsync(uri, HttpContent, fileName)メソッド
- [System.Net.Http.MultipartFormDataContect](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.multipartformdatacontent?view=net-5.0)（HttpContentの派生クラス）

要は、MultipartFromDataContenctにデータを追加していき、HttpClientのPostAsyncメソッドの引数として渡すだけです。（MultipartFormDataContentはHttpContentクラスを継承している）

# 実践-条件

それではやり方の一例を見ていきます。
今回は、以下のコンテンツをPostで送りたいとします。

**Postで送るもの**

- 画像データと、その名前を一度にPostする
    - 画像データ(image)：バイナリデータ
    - データ名(name)："hogehoge"

**Post結果の確認方法**

今回の例では、Post結果の確認に[httpbin](http://httpbin.org/#/HTTP_Methods/post_post)を使わせてもらいます。
httpbinは、指定のURLにHTTPリクエストを送ると、送信されたパラメータなどの情報をレスポンスとして返してくれるWebサービス&ソフトウェアです。

**以下のJsonがPostのResponseとして返ってくれば成功**  

今回の場合は、http://httpbin.org/post にPOSTリクエストを送り、以下のようなJSONがReponseとして返ってくれば成功です。
確認するポイントは2つです。

- "files"内の"image"に指定したデータ（今回だと画像をバイト配列として送るのでバイト配列）が入っているかどうか
- "form"内の"name"に指定した名前(hogehoge)が入っているどうか

```json-doc

{
	"args": {},
	"data": "",
	"files": {
		"image": "(～byteの羅列がここに入る。長いので省略～）"
	},
	"form": {
		"name": "hogehoge"
	},
	"headers": {
		"Content-Length": "13515",
		"Content-Type": "multipart/form-data; boundary="5cc407cc-3e4e-4d4a-8f67-fd2f29436423"",
		"Host": "httpbin.org",
		"X-Amzn-Trace-Id": "Root=1-5fc1ad93-3233ba1c3f705dd867fdf9c9"
	},
	"json": null,
	"origin": "126.99.210.176",
	"url": "http://httpbin.org/post"
}
```

# 実行環境

- Windows10
- C#8.0 (.NET Core3.1)

# 実践  
本題のコードが以下です。

```csharp

// MultipartFormDataContentのインスタンスをつくる。
using MultipartFormDataContent multiContent = new MultipartFormDataContent();

// コンテンツを、それぞれ専用の形式でインスタンス化する。
// imageBytes変数には本当は画像をバイナリ化したものが代入されている想定。
// ここでは簡単のため適当なバイナリを入れておく。
using ByteArrayContent imageContent = new ByteArrayContent(new byte[4] { 0, 1, 2, 3 });
using StringContent nameContent = new StringContent("hogehoge");

// 専用の形式にしたコンテンツを、MultipartFormDataContentにAddしていく。
multiContent.Add(imageContent, "image", "imageData");
multiContent.Add(nameContent, "name");

// HttpClientでPostする。
// （本当はHttpClientは都度インスタンスを生成するのではなく、アプリケーション内で使いまわしたほうがよい。）
using HttpClient client = new HttpClient();
var uri = new Uri("http://httpbin.org/post");
var msg = await client.PostAsync(uri, multiContent );

// Responseの表示
var responseContent = await msg.Content.ReadAsStringAsync();
Console.WriteLine($"{responseContent}");
```

コードについて解説していきます。

- MultipartFormDataContentのインスタンスをつくる

```csharp
// MultipartFormDataContentのインスタンスをつくる。
using MultipartFormDataContent multiContent = new MultipartFormDataContent();
```

ここは特に解説の必要もないと思います。
MultipartFormDataContentの派生大元であるHttpContentクラスはIDisposableを継承しているので、usingをつけて勝手に開放してくれるようにしておきます。
（この記事の趣旨とは関係ないですが、C#8.0から、[using変数宣言](https://ufcpp.net/study/csharp/oo_dispose.html#expression-using)により、その変数のスコープに基づいて自動開放してくれるようになりました。usingステートメントによる多重{}はコードが見にくくなるので、ここではusing変数宣言を使っています。）

- コンテンツをそれぞれ専用の形式でインスタンス化する

```csharp
// コンテンツを、それぞれ専用の形式でインスタンス化する。
// imageBytes変数には本当は画像をバイナリ化したものが代入されている想定。
// ここでは簡単のため適当なバイナリを入れておく。
using ByteArrayContent imageContent = new ByteArrayContent(new byte[4] { 0, 1, 2, 3 });
using StringContent nameContent = new StringContent("hogehoge");
```

MultipartFormDataContentに追加していく（もといHttpClientのコンテントとして扱う）ためには、各種コンテンツを専用の形式にしていく必要あります。
専用の形式とは、[System.Net.Http.HttpClient](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpcontent?view=net-5.0)クラスの派生クラスのことです。

ここでは、画像データ（それを事前にバイナリ化したもの）と文字列をそれぞれHttpContent化します。画像データはByteArrayContentクラス、文字列はStringContentクラスでHttpContent化します。（ここでは結果が見やすいよう画像データには適当なバイナリをいれてます。）

この部分は送りたいデータの形式に合わせて変えます。（他にはStreamContentクラスや、JsonContentクラスがあります。詳細は[HttpContentクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpcontent?view=net-5.0)のドキュメントを読んでください。）

ちなみに、MultipartFormDataContentクラスももちろんHttpContentクラスの派生クラスです。（正確にはMultipartFormDataContentクラス → MultipartContentクラス → HttpContentクラスという継承関係）

- 専用の形式にしたコンテンツをMultipartFormDataContentにAddしていく

```csharp
// 専用の形式にしたコンテンツを、MultipartFormDataContentにAddしていく。
multiContent.Add(imageContent, "image", "imageData");
multiContent.Add(nameContent, "name");
```

上で専用の形式（HttpContentクラスの派生クラス）にしたデータを追加していくだけです。
MultipartFormDataContent.Add(HttpContent, string)メソッドを使って追加していきます。

ここで、画像データと文字列データでは、引数の異なるAdd()メソッドを使っています。

[MultipartFormDataContent.Add()](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.multipartformdatacontent.add?view=net-5.0#System_Net_Http_MultipartFormDataContent_Add_System_Net_Http_HttpContent_System_String_)メソッドには、引数のとり方が3種類あります。

1. HttpContentのみ
2. HttpContentとString（HTTPコンテンツの名前）
3. HttpContentとStringとString（HTTPコンテンツの名前とコレクションに追加する HTTP コンテンツのファイル名）

1, 2番目の方法だとコンテンツがRequestの"form"に含まれ、3番目の方法だとコンテンツがRequestの"files"に含まれます。

今回は、画像データは"files"に含まれて欲しいので3番目、文字列データは"forms"に含まれて欲しいので2番目のメソッドを使います。なお、3番目の方法の場合は、引数の3つ目に文字列が必要ですがこれは今回の場合何でもよいです。
(ちなみに1番目の方法を使った場合は、コンテンツはform"に含まれ、そのkeyにあたるものがnullとなります。）

- POSTする

```csharp
// HttpClientでPostする。
// （本当はHttpClientは都度インスタンスを生成するのではなく、アプリケーション内で使いまわしたほうがよい。）
using HttpClient client = new HttpClient();
var uri = new Uri("http://httpbin.org/post");
var msg = await client.PostAsync(uri, multiContent );

// Responseの表示
var responseContent = await msg.Content.ReadAsStringAsync();
Console.WriteLine($"{responseContent}");
```

通常通りHttpClientのPostAsync()メソッドを使ってPostするだけです。
（上のコードではPostとそのResponseの表示をしています。）

**結果** 
コードを実行すると以下がResponseとして返ってきました。
大丈夫そうですね。

- "files"内の"image"に指定したデータが入っている("\u0000\u0001\u0002\u0003"は1, 2, 3, 4をそれぞれ16進数表記にしたもの）
- "form"内の"name"に指定した名前(hogehoge)が入っている

```json-doc

{
	"args": {},
	"data": "",
	"files": {
		"image": "\u0000\u0001\u0002\u0003"
	},
	"form": {
		"name": "hogehoge"
	},
	"headers": {
		"Content-Length": "13515",
		"Content-Type": "multipart/form-data; boundary="5cc407cc-3e4e-4d4a-8f67-fd2f29436423"",
		"Host": "httpbin.org",
		"X-Amzn-Trace-Id": "Root=1-5fc1ad93-3233ba1c3f705dd867fdf9c9"
	},
	"json": null,
	"origin": "126.99.210.176",
	"url": "http://httpbin.org/post"
}
```

# まとめ

この記事では、C#での複数コンテンツのPost方法を実例付きで解説しました。

簡単にやり方をまとめると以下です。

- MultipartFormDataContentのインスタンスをつくる
- コンテンツをそれぞれ専用の形式でインスタンス化する
- 専用の形式にしたコンテンツをMultipartFormDataContentにAddしていく
    - 追加したい形式によって複数あるAddメソッドを使い分ける
- POSTする

# リンク
- [httpbin.org](http://httpbin.org/)
- [マイクロソフトドキュメント-HttpClientクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpclient?view=net-5.0)
- [マイクロソフトドキュメント-HttpContentクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpcontent?view=net-5.0)
- [マイクロソフトドキュメント-MultipartFormDataContentクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.multipartformdatacontent?view=net-5.0)
    - [マイクロソフトドキュメント-MultipartFormDataConten.Addメソッド](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.multipartformdatacontent.add?view=net-5.0#System_Net_Http_MultipartFormDataContent_Add_System_Net_Http_HttpContent_System_String_)