[C#]使用WebClient上传文件并同时Post表单数据字段到服务端

Posted hello world

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C#]使用WebClient上传文件并同时Post表单数据字段到服务端相关的知识,希望对你有一定的参考价值。

转自: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;
        }

        
        /**/C#客户端用WebClient上传文件至IIS服务器,报错:远程服务器返回错误: (404) 未找到

通过 WebClient 使用 POST 值上传文件

使用 webclient 类将文件上传到文件服务器

使用WebClient进行文件上传

C# WebClient 禁用缓存

使用 C# Windows Forms 和 webclient 下载文件