[C#]使用WebClient上傳檔案並同時Post表單資料欄位到服務端

happymagic發表於2014-07-31

轉自:http://www.97world.com/archives/2963

   之前遇到一個問題,就是使用WebClient上傳檔案的同時,還要Post表單資料欄位,一開始以為WebClient可以直接做到,結果發現如果先Post表單欄位,就只能獲取到欄位及其值,如果先上傳檔案,也只能獲取到上傳檔案的內容。測試了不少時間才發現WebClient不能這麼使用。

    Google到相關的解決思路和類,因為發現網上的一些文章不是介紹得太簡單就是太複雜,所以這裡簡單整理一下,既能幫助自己鞏固知識,也希望能夠幫到大家!如果大家有什麼不明白,可以直接留言問我。

    關於WebClient上傳檔案並同時Post表單資料的實現原理,大家可以參考這篇文章http://www.cnblogs.com/goody9807/archive/2007/06/06/773735.html,介紹得非常詳細,但是類和例項有些模糊,所以類和例項可以直接參考本文。

HttpRequestClient類Code:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Net;
 
namespace Common.Helper
{
  /// <summary>
  /// description:http post請求客戶端
  /// last-modified-date:2012-02-28
  /// </summary>
  public class HttpRequestClient
  {
    #region //欄位
    private ArrayList bytesArray;
    private Encoding encoding = Encoding.UTF8;
    private string boundary = String.Empty;
    #endregion
 
    #region //構造方法
    public HttpRequestClient()
    {
      bytesArray = new ArrayList();
      string flag = DateTime.Now.Ticks.ToString("x");
      boundary = "---------------------------" + flag;
    }
    #endregion
 
    #region //方法
    /// <summary>
    /// 合併請求資料
    /// </summary>
    /// <returns></returns>
    private byte[] MergeContent()
    {
      int length = 0;
      int readLength = 0;
      string endBoundary = "--" + boundary + "--\r\n";
      byte[] endBoundaryBytes = encoding.GetBytes(endBoundary);
 
      bytesArray.Add(endBoundaryBytes);
 
      foreach (byte[] b in bytesArray)
      {
        length += b.Length;
      }
 
      byte[] bytes = new byte[length];
 
      foreach (byte[] b in bytesArray)
      {
        b.CopyTo(bytes, readLength);
        readLength += b.Length;
      }
 
      return bytes;
    }
 
    /// <summary>
    /// 上傳
    /// </summary>
    /// <param name="requestUrl">請求url</param>
    /// <param name="responseText">響應</param>
    /// <returns></returns>
    public bool Upload(String requestUrl, out String responseText)
    {
      WebClient webClient = new WebClient();
      webClient.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
 
      byte[] responseBytes;
      byte[] bytes = MergeContent();
 
      try
      {
        responseBytes = webClient.UploadData(requestUrl, bytes);
        responseText = System.Text.Encoding.UTF8.GetString(responseBytes);
        return true;
      }
      catch (WebException ex)
      {
        Stream responseStream = ex.Response.GetResponseStream();
        responseBytes = new byte[ex.Response.ContentLength];
        responseStream.Read(responseBytes, 0, responseBytes.Length);
      }
      responseText = System.Text.Encoding.UTF8.GetString(responseBytes);
      return false;
    }
 
    /// <summary>
    /// 設定表單資料欄位
    /// </summary>
    /// <param name="fieldName">欄位名</param>
    /// <param name="fieldValue">欄位值</param>
    /// <returns></returns>
    public void SetFieldValue(String fieldName, String fieldValue)
    {
      string httpRow = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}\r\n";
      string httpRowData = String.Format(httpRow, fieldName, fieldValue);
 
      bytesArray.Add(encoding.GetBytes(httpRowData));
    }
 
    /// <summary>
    /// 設定表單檔案資料
    /// </summary>
    /// <param name="fieldName">欄位名</param>
    /// <param name="filename">欄位值</param>
    /// <param name="contentType">內容內型</param>
    /// <param name="fileBytes">檔案位元組流</param>
    /// <returns></returns>
    public void SetFieldValue(String fieldName, String filename, String contentType, Byte[] fileBytes)
    {
      string end = "\r\n";
      string httpRow = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
      string httpRowData = String.Format(httpRow, fieldName, filename, contentType);
 
      byte[] headerBytes = encoding.GetBytes(httpRowData);
      byte[] endBytes = encoding.GetBytes(end);
      byte[] fileDataBytes = new byte[headerBytes.Length + fileBytes.Length + endBytes.Length];
 
      headerBytes.CopyTo(fileDataBytes, 0);
      fileBytes.CopyTo(fileDataBytes, headerBytes.Length);
      endBytes.CopyTo(fileDataBytes, headerBytes.Length + fileBytes.Length);
 
      bytesArray.Add(fileDataBytes);
    }
    #endregion
  }
}
客戶端例項程式碼:
01
02
03
04
05
06
07
08
09
10
string fileFullName=@"c:\test.txt",filedValue="hello_world",responseText = "";
FileStream fs = new FileStream(fileFullName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
byte[] fileBytes = new byte[fs.Length];
fs.Read(fileBytes, 0, fileBytes.Length);
fs.Close(); fs.Dispose();
 
HttpRequestClient httpRequestClient = new HttpRequestClient();
httpRequestClient.SetFieldValue("key", filedValue);
httpRequestClient.SetFieldValue("uploadfile", Path.GetFileName(fileFullName), "application/octet-stream", fileBytes);
httpRequestClient.Upload(NormalBotConfig.Instance.GetUploadFileUrl(), out responseText);
服務端例項程式碼:
1
2
3
4
5
6
7
8
if (HttpContext.Current.Request.Files.AllKeys.Length > 0)
{
  string filePath = Path.Combine(HttpContext.Current.Server.MapPath("~/"), "UploadFile", DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString(), DateTime.Now.Day.ToString());
  if (!Directory.Exists(filePath)) Directory.CreateDirectory(filePath);
  //這裡我直接用索引來獲取第一個檔案,如果上傳了多個檔案,可以通過遍歷HttpContext.Current.Request.Files.AllKeys取“key值”,再通過HttpContext.Current.Request.Files[“key值”]獲取檔案
  HttpContext.Current.Request.Files[0].SaveAs(Path.Combine(filePath, HttpContext.Current.Request.Files[0].FileName));
  string filedValue = HttpContext.Current.Request.Form["key"];
}


使用WebClient或HttpWebRequest模擬上傳檔案和資料

假如某網站有個表單,例如(url: http://localhost/login.aspx):
帳號  
密碼  

我們需要在程式中提交資料到這個表單,對於這種表單,我們可以使用 WebClient.UploadData 方法來實現,將所要上傳的資料拼成字元即可,程式很簡單:

string uriString = "http://localhost/login.aspx";
// 建立一個新的 WebClient 例項.
WebClient myWebClient = new WebClient();
string postData = "Username=admin&Password=admin";
// 注意這種拼字串的ContentType
myWebClient.Headers.Add("Content-Type","application/x-www-form-urlencoded");
// 轉化成二進位制陣列
byte[] byteArray = Encoding.ASCII.GetBytes(postData);
// 上傳資料,並獲取返回的二進位制資料.
byte[] responseArray = myWebClient.UploadData(uriString,"POST",byteArray);


對於檔案上傳類的表單,例如(url: http:
//localhost/uploadFile.aspx):
檔案  

對於這種表單,我們可以使用
String uriString 
= "http://localhost/uploadFile.aspx";

// 建立一個新的 WebClient 例項.
WebClient myWebClient = new WebClient();

string fileName = @"C:\upload.txt";

// 直接上傳,並獲取返回的二進位制資料.
byte[] responseArray = myWebClient.UploadFile(uriString,"POST",fileName);


還有一種表單,不僅有文字,還有檔案,例如(url: http:
//localhost/uploadData.aspx):
檔名  
檔案  

對於這種表單,似乎前面的兩種方法都不能適用,對於第一種方法,不能直接拼字串,對於第二種,我們只能傳檔案,重新回到第一個方法,注意引數:
public byte[] UploadData(
   
string address,
   
string method,
   
byte[] data
);
在第一個例子中,是通過拼字串來得到byte[] data引數值的,對於這種表單顯然不行,反過來想想,對於uploadData.aspx這樣的程式來說,直接通過網頁提交資料,後臺所獲取到的流是什麼樣的呢?(在我以前的一篇blog中,曾分析過這個問題:asp無元件上傳進度條解決方案),最終的資料如下:

-----------------------------7d429871607fe
Content
-Disposition: form-data; name="file1"; filename="G:\homepage.txt"
Content
-Type: text/plain
寶玉:http:
//www.webuc.net
-----------------------------7d429871607fe
Content
-Disposition: form-data; name="filename"
default filename
-----------------------------7d429871607fe--


所以只要拼一個這樣的byte[] data資料Post過去,就可以達到同樣的效果了。但是一定要注意,對於這種帶有檔案上傳的,其ContentType是不一樣的,例如上面的這種,其ContentType為
"multipart/form-data; boundary=---------------------------7d429871607fe"。有了ContentType,我們就可以知道boundary(就是上面的"---------------------------7d429871607fe"),知道boundary了我們就可以構造出我們所需要的byte[] data了,最後,不要忘記,把我們構造的ContentType傳到WebClient中(例如:webClient.Headers.Add("Content-Type", ContentType);)這樣,就可以通過WebClient.UploadData 方法上載檔案資料了。

具體程式碼如下:
生成二進位制資料類的封裝

using System;
using System.Web;
using System.IO;
using System.Net;
using System.Text;
using System.Collections;

namespace UploadData.Common
{
    
/**//// <summary>
    
/// 建立WebClient.UploadData方法所需二進位制陣列
    
/// </summary>

    public class CreateBytes
    
{
        Encoding encoding 
= Encoding.UTF8;

        
/**//// <summary>
        
/// 拼接所有的二進位制陣列為一個陣列
        
/// </summary>
        
/// <param name="byteArrays">陣列</param>
        
/// <returns></returns>
        
/// <remarks>加上結束邊界</remarks>

        public byte[] JoinBytes(ArrayList byteArrays)
        
{
            
int length = 0;
            
int readLength = 0;

            
// 加上結束邊界
            string endBoundary = Boundary + "--\r\n"//結束邊界
            byte[] endBoundaryBytes = encoding.GetBytes(endBoundary);
            byteArrays.Add(endBoundaryBytes);

            
foreach(byte[] b in byteArrays)
            
{
                length 
+= b.Length;
            }

            
byte[] bytes = new byte[length];

            
// 遍歷複製
            
//
            foreach(byte[] b in byteArrays)
            
{
                b.CopyTo(bytes, readLength);
                readLength 
+= b.Length;
            }


            
return bytes;
        }


        
public bool UploadData(string uploadUrl, byte[] bytes, out byte[] responseBytes)
        
{
            WebClient webClient 
= new WebClient();
            webClient.Headers.Add(
"Content-Type", ContentType);

            
try
            
{
                responseBytes 
= webClient.UploadData(uploadUrl, bytes);
                
return true;
            }

            
catch (WebException ex)
            
{
                Stream resp 
= ex.Response.GetResponseStream();
                responseBytes 
= new byte[ex.Response.ContentLength];
                resp.Read(responseBytes, 
0, responseBytes.Length);                
            }

            
return false
        }




        
/**//// <summary>
        
/// 獲取普通表單區域二進位制陣列
        
/// </summary>
        
/// <param name="fieldName">表單名</param>
        
/// <param name="fieldValue">表單值</param>
        
/// <returns></returns>
        
/// <remarks>
        
/// -----------------------------7d52ee27210a3c\r\nContent-Disposition: form-data; name=\"表單名\"\r\n\r\n表單值\r\n
        
/// </remarks>

        public byte[] CreateFieldData(string fieldName, string fieldValue)
        
{
            
string textTemplate = Boundary + "\r\nContent-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}\r\n";
            
string text = String.Format(textTemplate, fieldName, fieldValue);
            
byte[] bytes = encoding.GetBytes(text);
            
return bytes;
        }


        
        
/**//// <summary>
        
/// 獲取檔案上傳表單區域二進位制陣列
        
/// </summary>
        
/// <param name="fieldName">表單名</param>
        
/// <param name="filename">檔名</param>
        
/// <param name="contentType">檔案型別</param>
        
/// <param name="contentLength">檔案長度</param>
        
/// <param name="stream">檔案流</param>
        
/// <returns>二進位制陣列</returns>

        public byte[] CreateFieldData(string fieldName, string filename,string contentType, byte[] fileBytes)
        
{
            
string end = "\r\n";
            
string textTemplate = Boundary + "\r\nContent-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
            
            
// 頭資料
            string data = String.Format(textTemplate, fieldName, filename, contentType);
            
byte[] bytes = encoding.GetBytes(data);

            

            
// 尾資料
            byte[] endBytes = encoding.GetBytes(end);

            
// 合成後的陣列
            byte[] fieldData = new byte[bytes.Length + fileBytes.Length + endBytes.Length];

            bytes.CopyTo(fieldData, 
0); // 頭資料
            fileBytes.CopyTo(fieldData, bytes.Length); // 檔案的二進位制資料
            endBytes.CopyTo(fieldData, bytes.Length + fileBytes.Length); // \r\n

            
return fieldData;
        }



        屬性
屬性
    }

}



在Winform中呼叫


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

using UploadData.Common;
using System.IO;

namespace UploadDataWin
{
    
/**//// <summary>
    
/// frmUpload 的摘要說明。
    
/// </summary>

    public class frmUpload : System.Windows.Forms.Form
    
{
        
private System.Windows.Forms.Label lblAmigoToken;
        
private System.Windows.Forms.TextBox txtAmigoToken;
        
private System.Windows.Forms.Label lblFilename;
        
private System.Windows.Forms.TextBox txtFilename;
        
private System.Windows.Forms.Button btnBrowse;
        
private System.Windows.Forms.TextBox txtFileData;
        
private System.Windows.Forms.Label lblFileData;
        
private System.Windows.Forms.Button btnUpload;
        
private System.Windows.Forms.OpenFileDialog openFileDialog1;
        
private System.Windows.Forms.TextBox txtResponse;
        
/**//// <summary>
        
/// 必需的設計器變數。
        
/// </summary>

        private System.ComponentModel.Container components = null;

        
public frmUpload()
        
{
            
//
            
// Windows 窗體設計器支援所必需的
            
//
            InitializeComponent();

            
//
            
// TODO: 在 InitializeComponent 呼叫後新增任何建構函式程式碼
            
//
        }


        
/**//// <summary>
        
/// 清理所有正在使用的資源。
        
/// </summary>

        protected override void Dispose( bool disposing )
        
{
            
if( disposing )
            
{
                
if (components != null
                
{
                    components.Dispose();
                }

            }

            
base.Dispose( disposing );
        }


        Windows 窗體設計器生成的程式碼
#region Windows 窗體設計器生成的程式碼
        
/**//// <summary>
        
/// 設計器支援所需的方法 - 不要使用程式碼編輯器修改
        
/// 此方法的內容。
        
/// </summary>

        private void InitializeComponent()
        
{
            
this.lblAmigoToken = new System.Windows.Forms.Label();
            
this.txtAmigoToken = new System.Windows.Forms.TextBox();
            
this.lblFilename = new System.Windows.Forms.Label();
            
this.txtFilename = new System.Windows.Forms.TextBox();
            
this.btnBrowse = new System.Windows.Forms.Button();
            
this.txtFileData = new System.Windows.Forms.TextBox();
            
this.lblFileData = new System.Windows.Forms.Label();
            
this.btnUpload = new System.Windows.Forms.Button();
            
this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
            
this.txtResponse = new System.Windows.Forms.TextBox();
            
this.SuspendLayout();
            
// 
            
// lblAmigoToken
            
// 
            this.lblAmigoToken.Location = new System.Drawing.Point(4048);
            
this.lblAmigoToken.Name = "lblAmigoToken";
            
this.lblAmigoToken.Size = new System.Drawing.Size(7223);
            
this.lblAmigoToken.TabIndex = 0;
            
this.lblAmigoToken.Text = "AmigoToken";
            
// 
            
// txtAmigoToken
            
// 
            this.txtAmigoToken.Location = new System.Drawing.Point(12048);
            
this.txtAmigoToken.Name = "txtAmigoToken";
            
this.txtAmigoToken.Size = new System.Drawing.Size(24821);
            
this.txtAmigoToken.TabIndex = 1;
            
this.txtAmigoToken.Text = "";
            
// 
            
// lblFilename
            
// 
            this.lblFilename.Location = new System.Drawing.Point(4096);
            
this.lblFilename.Name = "lblFilename";
            
this.lblFilename.Size = new System.Drawing.Size(8023);
            
this.lblFilename.TabIndex = 2;
            
this.lblFilename.Text = "Filename";
            
// 
            
// txtFilename
            
// 
            this.txtFilename.Location = new System.Drawing.Point(12096);
            
this.txtFilename.Name = "txtFilename";
            
this.txtFilename.Size = new System.Drawing.Size(24821);
            
this.txtFilename.TabIndex = 3;
            
this.txtFilename.Text = "";
            
// 
            
// btnBrowse
            
// 
            this.btnBrowse.Location = new System.Drawing.Point(296144);
            
this.btnBrowse.Name = "btnBrowse";
            
this.btnBrowse.TabIndex = 4;
            
this.btnBrowse.Text = "瀏覽";
            
this.btnBrowse.Click += new System.EventHandler(this.btnBrowse_Click);
            
// 
            
// txtFileData
            
// 
            this.txtFileData.Location = new System.Drawing.Point(120144);
            
this.txtFileData.Name = "txtFileData";
            
this.txtFileData.Size = new System.Drawing.Size(16821);
            
this.txtFileData.TabIndex = 5;
            
this.txtFileData.Text = "";
            
// 
            
// lblFileData
            
// 
            this.lblFileData.Location = new System.Drawing.Point(40144);
            
this.lblFileData.Name = "lblFileData";
            
this.lblFileData.Size = new System.Drawing.Size(7223);
            
this.lblFileData.TabIndex = 6;
            
this.lblFileData.Text = "FileData";
            
// 
            
// btnUpload
            
// 
            this.btnUpload.Location = new System.Drawing.Point(48184);
            
this.btnUpload.Name = "btnUpload";
            
this.btnUpload.TabIndex = 7;
            
this.btnUpload.Text = "Upload";
            
this.btnUpload.Click += new System.EventHandler(this.btnUpload_Click);
            
// 
            
// txtResponse
            
// 
            this.txtResponse.Location = new System.Drawing.Point(136184);
            
this.txtResponse.Multiline = true;
            
this.txtResponse.Name = "txtResponse";
            
this.txtResponse.Size = new System.Drawing.Size(24872);
            
this.txtResponse.TabIndex = 8;
            
this.txtResponse.Text = "";
            
// 
            
// frmUpload
            
// 
            this.AutoScaleBaseSize = new System.Drawing.Size(614);
            
this.ClientSize = new System.Drawing.Size(400269);
            
this.Controls.Add(this.txtResponse);
            
this.Controls.Add(this.btnUpload);
            
this.Controls.Add(this.lblFileData);
            
this.Controls.Add(this.txtFileData);
            
this.Controls.Add(this.btnBrowse);
            
this.Controls.Add(this.txtFilename);
            
this.Controls.Add(this.lblFilename);
            
this.Controls.Add(this.txtAmigoToken);
            
this.Controls.Add(this.lblAmigoToken);
            
this.Name = "frmUpload";
            
this.Text = "frmUpload";
            
this.ResumeLayout(false);

        }

        
#endregion


        
/**//// <summary>
        
/// 應用程式的主入口點。
        
/// </summary>

        [STAThread]
        
static void Main() 
        
{
            Application.Run(
new frmUpload());
        }


        
private void btnUpload_Click(object sender, System.EventArgs e)
        
{
            
// 非空檢驗
            if (txtAmigoToken.Text.Trim() == "" || txtFilename.Text == "" || txtFileData.Text.Trim() == "")
            
{
                MessageBox.Show(
"Please fill data");
                
return;
            }


            
// 所要上傳的檔案路徑
            string path = txtFileData.Text.Trim();

            
// 檢查檔案是否存在
            if (!File.Exists(path)) 
            
{
                MessageBox.Show(
"{0} does not exist!", path);
                
return;
            }


            
// 讀檔案流
            FileStream fs = new FileStream(path, FileMode.Open,
                FileAccess.Read, FileShare.Read);
            
            
// 這部分需要完善
            string ContentType = "application/octet-stream";
            
byte[] fileBytes = new byte[fs.Length];
            fs.Read(fileBytes, 
0, Convert.ToInt32(fs.Length));


            
// 生成需要上傳的二進位制陣列
            CreateBytes cb = new CreateBytes();
            
// 所有表單資料
            ArrayList bytesArray = new ArrayList();
            
// 普通表單
            bytesArray.Add(cb.CreateFieldData("FileName", txtFilename.Text));
            bytesArray.Add(cb.CreateFieldData(
"AmigoToken", txtAmigoToken.Text));
            
// 檔案表單
            bytesArray.Add(cb.CreateFieldData("FileData", path
                                                , ContentType, fileBytes));

            
// 合成所有表單並生成二進位制陣列
            byte[] bytes = cb.JoinBytes(bytesArray);
            
            
// 返回的內容
            byte[] responseBytes;
            
            
// 上傳到指定Url
            bool uploaded = cb.UploadData("http://localhost/UploadData/UploadAvatar.aspx", bytes, out responseBytes);

            
// 將返回的內容輸出到檔案
            using (FileStream file = new FileStream(@"c:\response.text", FileMode.Create, FileAccess.Write, FileShare.Read))
            
{
                file.Write(responseBytes, 
0, responseBytes.Length);
            }


            txtResponse.Text 
= System.Text.Encoding.UTF8.GetString(responseBytes);

        }


        
private void btnBrowse_Click(object sender, System.EventArgs e)
        
{
            
if(openFileDialog1.ShowDialog() == DialogResult.OK)
            
{
                txtFileData.Text 
= openFileDialog1.FileName;
            }


        }

    }

}


相關文章