主要参考资料:
很详细的解释了客户端如何正确组装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