通过multipart/form-data标头同时传输表单和文件数据以及前后台代码

主要参考资料:

很详细的解释了客户端如何正确组装multipart/form-data头内容以及实体内容:https://www.cnblogs.com/kissdodog/archive/2013/04/06/3002779.html

 但是其中的代码有点问题:注意是在最后request流结束的boundary也要加上--,文章的注释中写了,但是代码没有写上;

在这里本人写的客户端代码:

//发送multipart-formdata
        private async void Btn_MF_Send_Click(object sender, EventArgs e)
        {
            try
            {
                string boundary = "----------" + DateTime.Now.Ticks.ToString("x");//元素分割标记 
                StringBuilder sb = new StringBuilder();
                sb.Append("--" + boundary);
                //这是第一条拼接的表单数据title:Lee
                sb.Append("
"); 
                sb.Append("Content-Disposition: form-data; name="title"");//新起一行前面必须"
"
                sb.Append("
");
                sb.Append("
");
                sb.Append("Lee");//value前面必须有2个换行  "
"
                sb.Append("
");
                sb.Append("--" + boundary);
                //这是第二条拼接的表单数据Name:张三
                sb.Append("
");
                sb.Append("Content-Disposition: form-data; name="Name"");
                sb.Append("
");
                sb.Append("
");
                sb.Append("张三");//value前面必须有2个换行  "
"
                sb.Append("
");
                sb.Append("--" + boundary);//boundary分隔符前面只需要一个"
"
                //下面是文件流数据
                sb.Append("
");
                sb.Append("Content-Disposition: form-data; name="11"; filename="11.docx" + """);
                sb.Append("
");
                sb.Append("Content-Type: application/octet-stream ");
                sb.Append("
");
                sb.Append("
");//value前面必须有2个换行 "
" 
                //这一行后面开始写文件内容

                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(TB_MF_Url.Text);
                request.ContentType = "multipart/form-data;boundary=" + boundary;//其它地方的boundary比这里在前面多--
                request.Method = "post";
                //文件流
                FileStream fs = new FileStream(@"E:UpLoad11.docx", FileMode.OpenOrCreate, FileAccess.Read);
                //post流的开始部份
                byte[] postStartBytes = Encoding.UTF8.GetBytes(sb.ToString());
                //post流的尾巴部份
                byte[] postEndBytes = Encoding.UTF8.GetBytes("
--" + boundary+"--
");//特别注意结尾符号也有--,否则将无法正确在服务端识别到结束符
                //post消息主体的长度
                request.ContentLength = postStartBytes.Length + fs.Length + postEndBytes.Length;
                //获取请求流
                Stream reqStream = await request.GetRequestStreamAsync();
                //写入开始部分
                await reqStream.WriteAsync(postStartBytes, 0, postStartBytes.Length);
                //文件流写入的缓冲区
                byte[] buff = new byte[checked(Math.Min(1024 * 1024 * 4, fs.Length))];
                //写入文件流
                int bytesRead = 0;
                while ((bytesRead=await fs.ReadAsync(buff,0,buff.Length))!=0)
                {
                    await reqStream.WriteAsync(buff, 0, bytesRead);
                }
                //写入消息主体的尾巴部分
                await reqStream.WriteAsync(postEndBytes, 0, postEndBytes.Length);

                reqStream.Close();
                fs.Close();
                //请求响应
                WebResponse response = await request.GetResponseAsync();
                Stream resStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(resStream);
                string res = await reader.ReadToEndAsync();

                TB_MF_RES.Text = res;
                reader.Close();
                resStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }           
        }
    }

服务端:

这里服务端可以用两种方法获取请求的消息:

方法一(推荐):

[HttpPost]
        [ActionName("UploadFile")]
        public string UploadFile()
        {
            //文件保存的服务器路径
            string root = "E:/UpLoad/Server/";
            if (!Directory.Exists(root))
            {
                Directory.CreateDirectory(root);
            }

            HttpRequest request = System.Web.HttpContext.Current.Request;
            //保存表单数据
            Dictionary<string, string> dic = new Dictionary<string, string>();
            var formData = request.Form;
            for (int i=0;i< formData.Count;i++)
            {
                string keyName = formData.GetKey(i);
                dic.Add(keyName, formData.Get(keyName));
            }
            //保存文件数据
            HttpFileCollection FileCollect = request.Files;           
            if (FileCollect.Count > 0)          //如果集合的数量大于0
            {
                foreach (string str in FileCollect)
                {
                    HttpPostedFile File = FileCollect[str];  //用key获取单个文件对象HttpPostedFile
                    //得到文件数据流,可以进行其他转存操作
                    Stream FileStream = File.InputStream;

                    string AbsolutePath = root + File.FileName;
                    File.SaveAs(AbsolutePath);              //将上传的东西保存
                }
            }

            string res = JsonConvert.SerializeObject(dic);
            return res;
        }

方法二:(更高的封装)

[HttpPost]
        public async Task<string> Post()
        {
            if (!Request.Content.IsMimeMultipartContent())
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }
            Dictionary<string, string> dic = new Dictionary<string, string>();
            //string root = HttpContext.Current.Server.MapPath("~/App_Data");//指定要将文件存入的服务器物理位置 
            string root = "E:/UpLoad/Server";
            if (!Directory.Exists(root))
            {
                Directory.CreateDirectory(root);
            }
            var provider = new MultipartFormDataStreamProvider(root);
            try
            {
                // Read the form data.  
                await Request.Content.ReadAsMultipartAsync(provider);

                // This illustrates how to get the file names.  
                foreach (MultipartFileData file in provider.FileData)
                {//接收文件  
                    Trace.WriteLine(file.Headers.ContentDisposition.FileName);//获取上传文件实际的文件名  
                    Trace.WriteLine("Server file path: " + file.LocalFileName);//获取上传文件在服务上默认的文件名  
                }//这样做直接就将文件存到了指定目录下,只接收文件数据流可通过HttpFileCollection操作 但并不保存至服务器的目录下,由开发自行指定如何存储,比如通过服务存到图片服务器  
                foreach (var key in provider.FormData.AllKeys)
                {//接收FormData  
                    dic.Add(key, provider.FormData[key]);
                }
            }
            catch(Exception ex)
            {
                return "Failed "+ex.Message;
                throw;
            }
            return "Success";
        }

客户端代码:

https://gitee.com/PapillonOfLee/httpwebrequest_post_file

服务端代码:

https://gitee.com/PapillonOfLee/HttpWebRequestSendFileServer

其它参考资料:

 怎么在后台解析File和表单数据:https://www.cnblogs.com/simadi/p/5032320.html

weiapi解析文件(左侧提示栏可以查看别的方案):https://blog.csdn.net/MDZZ666/article/details/89202548

清楚的解释了服务端是怎么读取multipart/form-data中的内容的,包括文件和表单数据:multipart/form-data:https://www.cnblogs.com/fwyTech/articles/3431015.html

End

原文地址:https://www.cnblogs.com/LeeSki/p/12403205.html