上传文件的示例

关于文件上传,整理一些示例代码如下:

一、使用 C# System.Net.HttpClient

使用Web服务将文件的字节数组内容上传到服务器。我们将采取以下步骤:

  • 读取文件的字节数组内容
  • 构造HttpRequestMessage对象
  • 设置请求消息头
  • 设置请求的多部分内容
  • 将请求发送到web服务。

步骤1:读取文件内容

private static void SendFileToServer(string fileFullPath)
{
  FileInfo fi = new FileInfo(fileFullPath);
  string fileName = fi.Name;
  byte[] fileContents = File.ReadAllBytes(fi.FullName);
}

步骤2:构造HttpRequestMessage对象

System.Net.Http命名空间中的HttpRequestMessage表示它所说的内容:HTTP请求。它是一个非常灵活的对象,允许您指定HTTP消息的Web方法、内容、标题和更多的属性。将以下代码添加到SendFileToServer(StringfileFullPath):

Uri webService = new Uri(@"http://avalidwebservice.com");
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, webService);
requestMessage.Headers.ExpectContinue = false;

最后一段代码,即将ExpetContinue设置为false的代码,意味着消息的Expect头将不包含继续。默认情况下,此属性设置为true。但是,许多服务器不知道如何处理“继续”值,它们会抛出异常。我在处理这个场景时遇到了这个问题,所以我会将其设置为false。这并不意味着每次使用HttpRequestMessage调用Web服务时都必须关闭该属性,但在我的情况下,它解决了一个显然令人费解的问题。

步骤3*设置http请求的多部分内容

应该在 MultipartFormDataContent对象的构造函数中指定多部分消息的边界字符串。这将设置多部分消息中各个部分的边界。然后,我们将向消息中添加一个字节数组内容,传递要上载的文件的字节。请注意,我们可以将以下参数添加到单个多部分消息:

  • 内容本身,例如字节数组内容
  • 该内容的名称:如果接收方需要搜索特定的名称,这是非常理想的。
  • 将添加到消息的内容处理标题中的文件名:这是web服务可以保存文件内容的名称。
    出于明显的原因,我们还指定内容类型头应该是 application/octet-stream。
MultipartFormDataContent multiPartContent = new MultipartFormDataContent("----MyGreatBoundary");
ByteArrayContent byteArrayContent = new ByteArrayContent(fileContents);
byteArrayContent.Headers.Add("Content-Type", "application/octet-stream");
multiPartContent.Add(byteArrayContent, "this is the name of the content", fileName);

requestMessage.Content = multiPartContent;

步骤4:将消息发送到Web服务并获得响应

我们现在可以使用System.Net.Http命名空间中的HttpClient对象向服务器发送消息了。我们还将从服务器获得响应。

HttpClient httpClient = new HttpClient();
Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
HttpResponseMessage httpResponse = httpRequest.Result;

我们可以使用HttpClient对象的SendAsync方法发送消息。它返回类型为HttpResponseMessage的任务,它表示将来将执行的任务。
请注意,此调用实际上不会向服务发送消息,这只是准备阶段。如果您熟悉任务并行库,那么这对您来说就不足为奇了-对服务的调用将在调用Task对象的结果属性时进行。

步骤5从服务器读取响应

使用HttpResponseMessage对象可以对服务响应进行详细的分析:状态码、响应内容、头等,响应内容可以是不同类型的:字节数组、表单数据、字符串、多部分、流。

HttpStatusCode statusCode = httpResponse.StatusCode;
HttpContent responseContent = httpResponse.Content;
 
if (responseContent != null)
{
    Task<String> stringContentsTask = responseContent.ReadAsStringAsync();
    String stringContents = stringContentsTask.Result;                    
}

当然,这取决于您如何处理字符串内容。

最终版本:

private static void SendFileToServer(string fileFullPath)
        {
            FileInfo fi = new FileInfo(fileFullPath);
            string fileName = fi.Name;
            byte[] fileContents = File.ReadAllBytes(fi.FullName);
            Uri webService = new Uri(@"http://avalidwebservice.com");
            HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, webService);
            requestMessage.Headers.ExpectContinue = false;
 
            MultipartFormDataContent multiPartContent = new MultipartFormDataContent("----MyGreatBoundary");
            ByteArrayContent byteArrayContent = new ByteArrayContent(fileContents);
            byteArrayContent.Headers.Add("Content-Type", "application/octet-stream");
            multiPartContent.Add(byteArrayContent, "this is the name of the content", fileName);
            requestMessage.Content = multiPartContent;
 
            HttpClient httpClient = new HttpClient();
            try
            {
                Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
                HttpResponseMessage httpResponse = httpRequest.Result;
                HttpStatusCode statusCode = httpResponse.StatusCode;
                HttpContent responseContent = httpResponse.Content;
 
                if (responseContent != null)
                {
                    Task<String> stringContentsTask = responseContent.ReadAsStringAsync();
                    String stringContents = stringContentsTask.Result;                    
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

二、使用 HttpWeRequest

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace HttpClientLib
{
    class Upload
    {
        /// <summary>
        /// 将本地文件上传到指定的服务器(HttpWebRequest方法)
        /// </summary>
        /// <param name="address">文件上传到的服务器</param>
        /// <param name="fileNamePath">要上传的本地文件(全路径)</param>
        /// <param name="saveName">文件上传后的名称</param>
        /// <param name="progressBar">上传进度条</param>
        /// <returns>服务器反馈信息</returns>
        private HttpWebRequest Upload_Request(string address, string fileNamePath, string saveName, ProgressBarprogressBar)
        {
            HttpWebRequest hwr;
            // 要上传的文件
            FileStream fs = new FileStream(fileNamePath, FileMode.Open, FileAccess.Read);
            BinaryReader r = new BinaryReader(fs);
            //时间戳
            string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x");
            byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + strBoundary + "\r\n");
            //请求头部信息
            StringBuilder sb = new StringBuilder();
            sb.Append("--");
            sb.Append(strBoundary);
            sb.Append("\r\n");
            sb.Append("Content-Disposition: form-data; name=\"");
            sb.Append("file");
            sb.Append("\"; filename=\"");
            sb.Append(saveName);
            sb.Append("\"");
            sb.Append("\r\n");
            sb.Append("Content-Type: ");
            sb.Append("application/octet-stream");
            sb.Append("\r\n");
            sb.Append("\r\n");
            string strPostHeader = sb.ToString();
            byte[] postHeaderBytes = Encoding.UTF8.GetBytes(strPostHeader);
            // 根据uri创建HttpWebRequest对象
            HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(new Uri(address));
            httpReq.Method = "POST";
            //对发送的数据不使用缓存【重要、关键】
            httpReq.AllowWriteStreamBuffering = false;
            //设置获得响应的超时时间(300秒)
            httpReq.Timeout = 300000;
            httpReq.ContentType = "multipart/form-data; boundary=" + strBoundary;
            long length = fs.Length + postHeaderBytes.Length + boundaryBytes.Length;
            long fileLength = fs.Length;
            httpReq.ContentLength = length;
            try
            {
                progressBar.Maximum = fileLength;//int.MaxValue;
                progressBar.Minimum = 0;
                progressBar.Value = 0;
                //每次上传4k
                int bufferLength = 4096;
                byte[] buffer = new byte[bufferLength];
                //已上传的字节数
                long offset = 0;
                //开始上传时间
                DateTime startTime = DateTime.Now;
                int size = r.Read(buffer, 0, bufferLength);
                Stream postStream = httpReq.GetRequestStream();
                //发送请求头部消息
                postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
                while (size > 0)
                {
                    postStream.Write(buffer, 0, size);
                    offset += size;
                    progressBar.Value = offset;//(int)(offset * (int.MaxValue / length));
                    TimeSpan span = DateTime.Now - startTime;
                    double second = span.TotalSeconds;
                    lblTime.Content = "已用时:" + second.ToString("F2") + "秒";
                    if (second > 0.0001)
                    {
                        lblSpeed.Content = " 平均速度:" + (offset / 1024 / second).ToString("0.00") + "KB/秒";
                    }
                    else
                    {
                        lblSpeed.Content = " 平均速度太快,系统放弃计算";
                    }
                    //lblState.Content = "已上传:" + (offset * 100.0 / length).ToString("F2") + "%";
                    lblState.Content = "已上传:" + (offset * 100.0 / fileLength).ToString("F2") + "%";
                    //1024*1024=1048576
                    if (fileLength > 1048576) //根据文件是否大于1M,来使用单位【处理精度】
                    {
                        lblSize.Content = (offset / 1048576.0).ToString("F2") + "M/" + (fileLength / 1048576.0).ToString("F2") + "M";
                    }
                    else
                    {
                        lblSize.Content = (offset / 1024.0).ToString("F2") + "KB/" + (fileLength / 1024.0).ToString("F2") +"KB";
                    }
                    size = r.Read(buffer, 0, bufferLength);
                }
                //添加尾部的时间戳
                postStream.Write(boundaryBytes, 0, boundaryBytes.Length);
                postStream.Close();
                //获取服务器端的响应
                WebResponse webRespon = httpReq.GetResponse();
                Stream s = webRespon.GetResponseStream();
                StreamReader sr = new StreamReader(s);
                //读取服务器端返回的消息
                string serverMsg = sr.ReadLine();
                hwr = JSSerialize.Deserialize<HttpWebRequestReturn>(serverMsg);
                s.Close();
                sr.Close();
            }
            catch(Exception ex)
            {
                hwr = new HttpWebRequestReturn();
                hwr.success = false;
                hwr.errors = ex.Message;
            }
            finally
            {
                fs.Close();
                r.Close();
            }
            return hwr;
        }
    }
}



三、使用 vb WinHttpRequest.5.1

Public Function PostFile(ByVal strurl As String, ByVal strFile As String, ByVal ContentType As String, ByRef strret As String) As Boolean
 
    Dim aHttpRequest        As WinHttpRequest
    Dim i As Integer
    Dim name As String, boundary As String
    Dim filecontent
    Dim sBody
 
    
    On Error GoTo errH
   
    ''创建WinHttp.WinHttpRequest
    Set aHttpRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
    
 
    aHttpRequest.Open "POST", strurl, False
    aHttpRequest.Option(WinHttpRequestOption_SslErrorIgnoreFlags) = &H3300
 
    '截取文件名称
    i = InStrRev(strFile, "\") + 1
    name = Mid(strFile, i)
    
    
    boundary = "----WebKitFormBoundaryaEHpMn3lywBtjPfE"
    
    filecontent = GetFile(strFile)
    sBody = BuildFormData(filecontent, name, boundary, ContentType)
    
    aHttpRequest.SetRequestHeader "Content-Type", "multipart/form-data; boundary=" & boundary
    aHttpRequest.SetRequestHeader "Content-Length", Len(sBody)
   
    aHttpRequest.Send sBody
    aHttpRequest.WaitForResponse
    strret = aHttpRequest.ResponseText
    Set aHttpRequest = Nothing
    PostFile = True
    Exit Function
errH:
    strret = Err.Description
    PostFile = False
End Function
 
'读取文件
Public Function GetFile(ByVal filename As String)
    Dim Stream: Set Stream = CreateObject("ADODB.Stream")
    Stream.Type = 1 'Binary
    Stream.Open
    Stream.LoadFromFile filename
    GetFile = Stream.Read
    Stream.Close
End Function
 
Public Function StringToMB(S)
    Dim i, B
    For i = 1 To Len(S)
      B = B & ChrB(Asc(Mid(S, i, 1)))
    Next
    StringToMB = B
End Function
 
Public Function BuildFormData(FileContents, ByVal filename As String, ByVal boundary As String, ByVal ContentType As String)
    Dim formdata As Variant
    Dim Pre As String, Po As String    ', 'ContentType
    
    
    'The two parts around file contents In the multipart-form data.
    Pre = "--" + boundary + vbCrLf + MapFields("file", filename, ContentType)
    Po = vbCrLf + "--" + boundary + "--" + vbCrLf
    
    'Build form data using recordset binary field
    Const adLongVarBinary = 205
    Dim RS: Set RS = CreateObject("ADODB.Recordset")
    RS.Fields.Append "b", adLongVarBinary, Len(Pre) + LenB(FileContents) + Len(Po)
    RS.Open
    RS.AddNew
      Dim LenData
      'Convert Pre string value To a binary data
      LenData = Len(Pre)
      RS("b").AppendChunk (StringToMB(Pre) & ChrB(0))
      Pre = RS("b").GetChunk(LenData)
      RS("b") = ""
     
      'Convert Po string value To a binary data
      LenData = Len(Po)
      RS("b").AppendChunk (StringToMB(Po) & ChrB(0))
      Po = RS("b").GetChunk(LenData)
      RS("b") = ""
     
      'Join Pre + FileContents + Po binary data
      RS("b").AppendChunk (Pre)
      RS("b").AppendChunk (FileContents)
      RS("b").AppendChunk (Po)
    RS.Update
    formdata = RS("b")
    RS.Close
    BuildFormData = formdata
End Function
 
Private Function MapFields(ByVal FieldName As String, ByVal filename As String, ByVal ContentType As String) As String
    Dim MPTemplate 'template For multipart header
    MPTemplate = "Content-Disposition: form-data; name=""{field}"";" + _
     " filename=""{file}""" + vbCrLf + _
     "Content-Type: {ct}" + vbCrLf + vbCrLf
    Dim Out
    Out = Replace(MPTemplate, "{field}", FieldName)
    Out = Replace(Out, "{file}", filename)
    MapFields = Replace(Out, "{ct}", ContentType)
End Function
 
 
Private Sub Command1_Click()
    Dim strret As String  '示例
    PostFile "http://www.***.com/index.php", "d:\test.pdf", "application/pdf", strret
    MsgBox strret
End Sub

四、需要关注的content-type

http协议是建立在tcp/ip协议之上的应用层协议,主要包括三个部分,状态行,头部信息,消息主体。对应一个http请求就是:请求行,请求头,请求体。

协议规定post提交的数据,必须包含在消息主体中entity-body中,但是协议并没有规定数据使用什么编码方式。开发者可以自己决定消息主体的格式。

数据发送出去后,需要接收的服务端解析成功,一般服务端会根据content-type字段来获取参数是怎么编码的,然后对应去解码。

application/x-www-form-urlencoded

在最开始的请求方式中,请求参数都是放在url中,表单提交的时候,都是以key=&value=的方式写在url后面。这也是浏览器表单提交的默认方式。

multipart/form-data

此种方式多用于文件上传,表单数据都保存在http的正文部分,各个表单项之间用boundary分开。

下面是一个完整的抓包数据

POST /ecard/uploadFaceImage?timestamp=1531906535406 HTTP/1.0
Host: www.example.com
X-Real-IP: 183.156.142.242
X-Forwarded-For: 183.156.142.242
Connection: close
Content-Length: 230101
sign: 9a7d3b4978979ef65a12e34ae1cf7b2d
accept: */*
user-agent: Mozilla/5.0 (Linux; U; Android 6.0.1; zh-CN; OPPO R9s Build/MMB29M) 
AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 
UCBrowser/11.8.8.968 UWS/2.13.1.42 Mobile Safari/537.36 UCBS/2.13.1.42_180629181124 
ChannelId(1) NebulaSDK/1.8.100112 Nebula AlipayDefined(nt:WIFI,ws:360|0|3.0) 
AliApp(AP/10.1.28.560) AlipayClient/10.1.28.560 Language/zh-Hans useStatusBar/true 
isConcaveScreen/false
Cookie: ssl_upgrade=0; spanner=6tlJA6NZwnkqTDN+BMhdT7lbzLPsFJUeXt2T4qEYgj0=
Accept-Encoding: gzip
Content-Type: multipart/form-data; boundary=pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G
 
--pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G
Content-Disposition: form-data; name="personCode"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit
 
DM1203
--pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G
Content-Disposition: form-data; name="DM1203"; filename="123524587.jpg"
Content-Type: 
Content-Transfer-Encoding: binary
  图片二进制数据(特别长)
  
--pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G--
HTTP/1.1 200 
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: Origin, No-Cache, X-Requested-With, 
If-Modified-Since, Pragma, Cache-Control, Expires, Content-Type
Access-Control-Allow-Credentials: true
XDomainRequestAllowed: 1
Content-Type: application/json;charset=UTF-8
Date: Wed, 18 Jul 2018 09:35:36 GMT
Connection: close
 
{"retCode":1,"msg":"success","data":null}

可以看到里面有一个boundary分界,值为:pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G,请求时,会放在Content-Type后面传到服务器,服务器根据这个边界解析数据,划分段,每一段都是一项数据。每一项中的name属性就是唯一的id

application/json

现在越来越多的应用使用 application/json,用来告诉服务端消息主体是序列化的json字符串。由于json规范的流行,各大浏览器都开始原生支持JSON.stringfy。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值