The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

利用 WebClient 類別模擬 HTTP POST 表單送出的注意事項

我們都知道 WebClient 類別是個簡單易用的東西,不只可以用作 HTTP 用途,連 FTP 都能用,想偷懶時很快就能寫出一些網路資料上傳、下載的程式,像我在寫一些測試程式時經常會使用 WebClient 類別,但大多情況都用來「下載網頁」居多,少有模擬表單上傳資料的情況,但利用 WebClient 類別在「傳送表單資料」時要小心使用,否則遠端接不到資料又很難除錯時哪就麻煩了。

與「上傳資料」有關的方法有以下四種 (不考慮非同步模式的方法)

如果是你,你會選用哪一種呢?你會不會覺得 string 比較友善呢? ^__^

像我們之前就這樣寫,以為可以順利的將資料上傳(錯誤示範):

using (WebClient wc = new WebClient())
{
  try
  {
	  wc.Encoding = Encoding.UTF8;

	  string resultXML = wc.UploadString(Config.PostURL, 
		  String.Format("id={0}&pw={1}", user.ID, user.PW));
  }
  catch (WebException ex)
  {
	  throw new Exception("無法連接遠端伺服器");
  }
}

透過這段程式所送出的 HTTP Request Header 如下:

POST /Home/Echo HTTP/1.1
Host: my.example.com:52215
Content-Length: 13
Connection: Keep-Alive

id=aaa&pw=bbb

雖然一樣是 POST 方法,但是缺少了最重要的 Content-Type 標頭,也就是:

Content-Type: application/x-www-form-urlencoded

以我們常用 ASP.NET MVC 為例,你不傳送標準的 Content-Type 過去,就無法正確抓到所有表單資料,也無法透過 Model Binder 自動繫結到 Action 參數中!

雖然你可以利用 wc.Headers 屬性自行將缺少的 Header 加上去,例如:

wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");

但後來我們改用 UploadValues 方法,程式碼範例如下:

using (WebClient wc = new WebClient())
{
  try
  {
      wc.Encoding = Encoding.UTF8;
      
      NameValueCollection nc = new NameValueCollection();
      nc["id"] = "aaa";
      nc["pw"] = "bbb";
      
      byte[] bResult = wc.UploadValues(Config.PostURL, nc);
      
      string resultXML = Encoding.UTF8.GetString(bResult);
  }
  catch (WebException ex)
  {
      throw new Exception("無法連接遠端伺服器");
  }
}

雖然多了幾行 Code,但是其實語法更容易理解,而且也不用自己組 POST 資料中類似 QueryString 的字串,還會自動做 URLEncode 動作,只是最後多一個步驟要將 byte[] 轉回 string 而已,這段程式最後會送出的 HTTP Request Header 如下:

POST /Home/Echo HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: my.example.com:52215
Content-Length: 13
Connection: Keep-Alive

id=aaa&pw=bbb

但是,使用 WebClient 類別其實有許多缺點,所以我文章一開頭就說過「想偷懶時」,主要的缺點有:

  • 無法指定 Timeout,所以當網路連不上時,網頁或程式會整個停在那裡很久!
  • 不適合用來下載大量的檔案,高負載的網站也不適合這樣用,即便你用非同步的方式撰寫,也會讓 WebClient 因為佔據過多 Threads 而導致效能降低,壹台電腦的 Threads 數是有限制的。

對於較正式的場合,還是建議改用 HttpWebRequest 類別處理。

相關連結