質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework 4.0

Microsoft Windows用のソフトウェア開発環境/実行環境である .NET Frameworkの4番目のメジャーバージョンです。

Q&A

解決済

3回答

16658閲覧

C#、HttpClientで複数データをPOSTしたい

ko-ito

総合スコア54

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework 4.0

Microsoft Windows用のソフトウェア開発環境/実行環境である .NET Frameworkの4番目のメジャーバージョンです。

2グッド

2クリップ

投稿2016/10/25 05:54

編集2016/10/26 02:49

###前提・実現したいこと
C#でサーバーにテキストデータをPOSTするプログラムを作っています。

複数のデータをHttpClientのPostAsyncで投げると、
最初のデータの中身がヘッダーよりも先に送信されてしまうため、通信が失敗します。

どうすれば正しく通信出来るのでしょうか。

-----以下10/26 追記-----
MultipartFormDataContentのAddメソッドについて、
例えば、下記のように書いた場合、

content.Add(new StringContent("test1"), "string1");

送信されるデータには

--boundary Content-Disposition: form-data; name=string1 test1 --boundary

というヘッダとデータが組になって追加される認識でしたが、

--boundary Content-Disposition: form-data; name=TestData; filename=TestFile Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=string1 TestMainData --boundary

上記のようにヘッダとデータの間に別のデータが挿入されたりするものなのでしょうか。

###発生している問題・エラーメッセージ

パケットキャプチャで実際にキャプチャ出来たデータ --boundary test1 --boundary Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=string2 test2 --boundary Content-Disposition: form-data; name=TestData; filename=TestFile Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=string1 TestMainData --boundary 想定しているデータ --boundary Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=string1 test1 --boundary Content-Type: text/plain; charset=utf-8 Content-Disposition: form-data; name=string2 test2 --boundary Content-Disposition: form-data; name=TestData; filename=TestFile TestMainData --boundary

###該当のソースコード

C#

1private async Task<bool> test() 2{ 3 var content = new MultipartFormDataContent(); 4 var mainData = new ByteArrayContent(Encoding.UTF8.GetBytes("TestMainData")); 5 mainData.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") 6 { 7 Name = "TestData", 8 FileName = "TestFile", 9 }; 10 11 content.Add(new StringContent("test1"), "string1"); 12 content.Add(new StringContent("test2"), "string2"); 13 content.Add(mainData); 14 15 using (var httpClient = new HttpClient()) 16 { 17 HttpResponseMessage response = await httpClient.PostAsync(targetUri, content); 18 return response.IsSuccessStatusCode; 19 } 20}

###試したこと
メインデータをAddせず、StringContent 2個だけを送信してみましたが、
Content-Disposition: form-data; name=string1
の行が最後に来てしまうため、通信エラーとなってしまいました。

###補足情報
OS:Windows10 Pro
開発環境:VisualStudio2015 Update3
System.Net.Http のランタイムバージョン:v4.0.30319

mo-ri, KSwordOfHaste👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

自己解決

解決方法

データ作成が終わったあと、送信する前にReadAsStringAsync メソッドで
contentの中身を参照する、もしくは
httpClientのDefaultRequestHeaders.ExpectContinue に
false を設定することで問題が発生しなくなることが分かりました。

原因は分からないままですが、
通信は出来るようになったため、解決とさせて頂きます。

プログラムの変更点

下記の通り2行追加しただけです。

C#

1~省略~ 2content.Add(mainData); 3 4using (var httpClient = new HttpClient()) 5{ 6 // 下記2行を追加(どちらか片方でも問題ないと思うが、念のため両方実施) 7 await content.ReadAsStringAsync(); 8 httpClient.DefaultRequestHeaders.ExpectContinue = false; 9~省略~

テスト結果

ReadAsStringAsyncを呼ぶ/呼ばない、
ExpectContinueにfalseをセットする/しないを乱数任せにして
100回ほどテストを行った結果は以下の通りです。

ExpectContinue の値 | true | false ----------------------------------------------------- ReadAsStringAsync 呼ぶ | 100%成功 | 100%成功 ReadAsStringAsync 呼ばない | 100%失敗 | 100%成功

投稿2016/10/30 02:20

ko-ito

総合スコア54

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2016/10/30 04:10

なるほど!multipartについて自分の方が貴重な勉強をさせていただきました。一般のwebサーバーでは任意の順序で送信される場合もサポートされているのだろうかとか自分も勉強したいと思います。先達はあらまほしき事なり・・・。ko-itoさん、coco_bauerさんありがとうございました。
guest

0

asyncというのは、非同期(asynchronized:動作順序を制約しない)実行を指示するものですから、ヘッダのPostと、データのPostのどちらが先になるのかを事前に知ることはできません。

順序どおりにデータを受け取るための方策には、次の2種類があります。
1)
asyncでpostする(どのデータが先に送られるのか判らない)
サーバ側では受け取ったデータをバッファに入れてゆき、並べ替えをして正しい順序のデータを得る
2)
asyncをやめて、データを順にPostする。
サーバ側では順序どおりにデータを受け取れる。

ko-itoさんにとって、asyncを使わなくてはならない事情があるのであれば1)の方法(サーバ側で対処する)が良いと思います。

特にasyncでなくても良いのであれば、2)の方法が素直な実装だと思います。

投稿2016/10/25 06:20

coco_bauer

総合スコア6915

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ko-ito

2016/10/25 07:11

解答ありがとうございます。 asyncの使用についてですが、 HttpClientクラスには非同期関数しか存在しません。 試しに await を使用せずに Result を参照してみましたが結果は変わりませんでした。 HttpClientクラスを使って順序通りにデータをPOSTするためには どのように実装するのが正しいのでしょうか。
KSwordOfHaste

2016/10/25 10:25

Task<T>を返すメソッドは非同期限定ではなく「非同期にも同期的にも呼び元で選択できる」という意味のものと思います。 T result = await XXXAsync(...); とすれば同期的に実行する関数とみなすことができます。await以外にもTask<T>型のWait()メソッドでも完了を待てます。要するに完了を待つようにしながら3つの送信を行えばお望みのことができると思います。coco_bauerさんがasyncを使わないとおっしゃっているのはXXXAsyncメソッドを使わないという意味ではなく、そのメソッドを同期的に使うという意味だと思いますよ。
ko-ito

2016/10/25 16:20

コメントありがとうございます。 申し訳ありません、 理解が不足しているようですので、 非同期メソッドについては勉強しておきます。 1点だけ教えてください。 今回のプログラムでは、 PostAsyncの部分にはawaitを入れて同期的に処理しているつもりだったのですが、 1つ目のデータのヘッダが3つ目のデータのデータ部に混ざって送信されてしまっています。 これを回避するにはプログラムのどの部分を修正すればよいのでしょうか。
guest

0

追記:本回答のコメントにも記載しましたが、この回答は問題のポイントをはずしているものだったようです。大変失礼しました。お恥ずかしい内容ですが、無暗に消すのはよくないのでこのまま残します。

失礼、上のコメントのawaitの説明は間違いでした。MSDNのawaitの説明を見ると書かれているようにawaitと書いても完了を待つのではなく一つ外側のtestメソッドが非同期メソッドの動きをするのでした。

自分で間違っておいてこういうのもなんですが(w;)非同期メソッドについての勉強を後回しにしてはいけないと思います。非同期メソッドやawaitの動きは理解が不十分なうちはかなり難解な動きなだけに、できる限り動きを把握した上でなければ先に進まないことが鉄則だと思います。

・・・とえらそうなことを言いましたが、自分も十分身についていない状態で中途半端にコメントしてしまいすみませんでした。はじのうわぬりになるかもですが、awaitをやめて次のようにすると同期的に実行できたように思います。実験してないのでもし間違いだったら申し訳ないです。ぜひとも正しいかどうか質問者さんご自身でも確認していただきたいと思います。

C#

1void bool test() { // <= testのsignatureをこうかえる 2 ... 3 using (...) { 4 Task<HttpResponseMessage> task = httpClient.PostAsync(targetUri, content); 5 task.Wait(); // <- 完了を明示的に待つ 6 HttpResponseMessage response = task.Result; // <- 完了したら結果を得る 7 return response.IsSuccessStatusCode; 8 } 9}

投稿2016/10/25 17:25

編集2016/10/26 08:27
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ko-ito

2016/10/26 01:50

解答ありがとうございます。 下記の通り修正し、テストをしてみたのですが、結果は一緒でした。 (MSDNによると「Task.Resultプロパティは非同期操作を完了するまでプロパティへのアクセスをブロックします。これはWaitメソッドを呼ぶのと同じです。」とあるので、Task.Wait()は省略しています。) private bool test() { ~中略~ HttpResponseMessage response = httpClient.PostAsync(targetUri, content).Result; ~中略~ } そもそもPostAsyncの動作について教えていただきたいのですが、 データをPOSTするまでは同期処理、 POSTした後、レスポンス待ちが別スレッドで実行される というように理解していたのですが、間違っているのでしょうか。
KSwordOfHaste

2016/10/26 08:29 編集

やはり恥を上塗りました。ここに至り質問者さんの悩みにようやく追いつけたかも知れません。coco_bauerさんがおっしゃる「同期処理にする」とはHttpClient#PostASyncではなく別のAPI(WebClientとかになるのでしょうか?)を使うという意味だったようです。重ね重ねすみません。自分はmultipart/form-dataは単一の通信路によりヘッダーと中身が順番に送信されるものと思っており、coco_bauerさんがおっしゃるようなヘッダーとデータの送信順序が保証されないという認識がありませんでした。RFCを少しあたったのですがHTTPプロトコルにそれを認めるようなものを見つけること自体ができませんでした。なお質問者さんと同じようにHttpClientとMultiPartFormDataContentによりポストしているサイトなども見たのですがヘッダーとデータは期待通りの順序で送られるとしか読み取れませんでした。この点自分も理解したいので調べたいと思いますが、早く動かすためには同期処理を試した方がよいのかも知れません。よくわからずにコメントしてしまい大変失礼いたしました。
ko-ito

2016/10/30 02:18

コメントありがとうございます。 WebClientでは同時に複数データを送るのが出来ませんでした。 HttpWebRequestを使えば問題なく通信出来ることは確認できていますが、 各種ヘッダーの設定(ContentLengthやContentTypeなど)が非常に煩雑なので、 HttpClientを使おうとおもいます。 親身になってご対応頂きとても感謝しています。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問