来自C#客户端的多部分表单

我正在尝试从C#客户端(Outlook插件)填写一个PHP应用程序中的窗体。 我使用Fiddler从php应用程序中查看原始请求,并将表单作为多部分/表单传输。 不幸的是,.net不支持这种types的表单(WebClient只有一个上传文件的方法)。 有没有人知道一个图书馆或有一些代码来实现这一目标? 我想发布不同的值,另外(但只是有时)文件。

谢谢你的帮助,塞巴斯蒂安

这是从我写的一些示例代码剪切和粘贴,希望它应该给基础。 它目前只支持文件数据和表单数据。

public class PostData { private List<PostDataParam> m_Params; public List<PostDataParam> Params { get { return m_Params; } set { m_Params = value; } } public PostData() { m_Params = new List<PostDataParam>(); // Add sample param m_Params.Add(new PostDataParam("email", "MyEmail", PostDataParamType.Field)); } /// <summary> /// Returns the parameters array formatted for multi-part/form data /// </summary> /// <returns></returns> public string GetPostData() { // Get boundary, default is --AaB03x string boundary = ConfigurationManager.AppSettings["ContentBoundary"].ToString(); StringBuilder sb = new StringBuilder(); foreach (PostDataParam p in m_Params) { sb.AppendLine(boundary); if (p.Type == PostDataParamType.File) { sb.AppendLine(string.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", p.Name, p.FileName)); sb.AppendLine("Content-Type: text/plain"); sb.AppendLine(); sb.AppendLine(p.Value); } else { sb.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", p.Name)); sb.AppendLine(); sb.AppendLine(p.Value); } } sb.AppendLine(boundary); return sb.ToString(); } } public enum PostDataParamType { Field, File } public class PostDataParam { public PostDataParam(string name, string value, PostDataParamType type) { Name = name; Value = value; Type = type; } public string Name; public string FileName; public string Value; public PostDataParamType Type; } 

要发送数据,您需要:

 HttpWebRequest oRequest = null; oRequest = (HttpWebRequest)HttpWebRequest.Create(oURL.URL); oRequest.ContentType = "multipart/form-data"; oRequest.Method = "POST"; PostData pData = new PostData(); byte[] buffer = encoding.GetBytes(pData.GetPostData()); // Set content length of our data oRequest.ContentLength = buffer.Length; // Dump our buffered postdata to the stream, booyah oStream = oRequest.GetRequestStream(); oStream.Write(buffer, 0, buffer.Length); oStream.Close(); // get the response oResponse = (HttpWebResponse)oRequest.GetResponse(); 

希望这一点清楚,我已经从几个来源削减和粘贴,以获得更整洁。

感谢大家的回答! 我最近必须得到这个工作,并大量使用你的build议。 然而,有一些棘手的部分没有按预期工作,主要是实际上包括文件(这是问题的重要部分)。 这里已经有很多答案了,但是我认为这对未来的某个人可能是有用的(我在网上找不到很多这样的例子)。 我写了一篇博客文章 ,解释一下。

基本上,我第一次尝试传递文件数据作为UTF8编码的string,但我有编码文件的问题(它工作正常的纯文本文件,但是当上传一个Word文档,例如,如果我试图保存使用Request.Files [0] .SaveAs()传递给发布表单的文件,在Word中打开文件无法正常工作,我发现如果直接使用Stream(而不是StringBuilder) ),它按预期工作,而且做了一些修改,使我更容易理解。

顺便说一下, 多部分表单请求注释和W3C推荐的mulitpart / form-data是一些有用的资源,以防任何人需要参考规范。

我将WebHelpers类更改FormUpload小一些,界面更简单,现在叫做FormUpload 。 如果传递一个FormUpload.FileParameter ,则可以传递byte []内容以及一个文件名和内容types,如果传递一个string,则将其视为标准名称/值组合。

这里是FormUpload类:

 // Implements multipart/form-data POST in C# http://www.ietf.org/rfc/rfc2388.txt // http://www.briangrinstead.com/blog/multipart-form-post-in-c public static class FormUpload { private static readonly Encoding encoding = Encoding.UTF8; public static HttpWebResponse MultipartFormDataPost(string postUrl, string userAgent, Dictionary<string, object> postParameters) { string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid()); string contentType = "multipart/form-data; boundary=" + formDataBoundary; byte[] formData = GetMultipartFormData(postParameters, formDataBoundary); return PostForm(postUrl, userAgent, contentType, formData); } private static HttpWebResponse PostForm(string postUrl, string userAgent, string contentType, byte[] formData) { HttpWebRequest request = WebRequest.Create(postUrl) as HttpWebRequest; if (request == null) { throw new NullReferenceException("request is not a http request"); } // Set up the request properties. request.Method = "POST"; request.ContentType = contentType; request.UserAgent = userAgent; request.CookieContainer = new CookieContainer(); request.ContentLength = formData.Length; // You could add authentication here as well if needed: // request.PreAuthenticate = true; // request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested; // request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.Encoding.Default.GetBytes("username" + ":" + "password"))); // Send the form data to the request. using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(formData, 0, formData.Length); requestStream.Close(); } return request.GetResponse() as HttpWebResponse; } private static byte[] GetMultipartFormData(Dictionary<string, object> postParameters, string boundary) { Stream formDataStream = new System.IO.MemoryStream(); bool needsCLRF = false; foreach (var param in postParameters) { // Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added. // Skip it on the first parameter, add it to subsequent parameters. if (needsCLRF) formDataStream.Write(encoding.GetBytes("\r\n"), 0, encoding.GetByteCount("\r\n")); needsCLRF = true; if (param.Value is FileParameter) { FileParameter fileToUpload = (FileParameter)param.Value; // Add just the first part of this param, since we will write the file data directly to the Stream string header = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\";\r\nContent-Type: {3}\r\n\r\n", boundary, param.Key, fileToUpload.FileName ?? param.Key, fileToUpload.ContentType ?? "application/octet-stream"); formDataStream.Write(encoding.GetBytes(header), 0, encoding.GetByteCount(header)); // Write the file data directly to the Stream, rather than serializing it to a string. formDataStream.Write(fileToUpload.File, 0, fileToUpload.File.Length); } else { string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}", boundary, param.Key, param.Value); formDataStream.Write(encoding.GetBytes(postData), 0, encoding.GetByteCount(postData)); } } // Add the end of the request. Start with a newline string footer = "\r\n--" + boundary + "--\r\n"; formDataStream.Write(encoding.GetBytes(footer), 0, encoding.GetByteCount(footer)); // Dump the Stream into a byte[] formDataStream.Position = 0; byte[] formData = new byte[formDataStream.Length]; formDataStream.Read(formData, 0, formData.Length); formDataStream.Close(); return formData; } public class FileParameter { public byte[] File { get; set; } public string FileName { get; set; } public string ContentType { get; set; } public FileParameter(byte[] file) : this(file, null) { } public FileParameter(byte[] file, string filename) : this(file, filename, null) { } public FileParameter(byte[] file, string filename, string contenttype) { File = file; FileName = filename; ContentType = contenttype; } } } 

这里是调用代码,它上传一个文件和一些正常的后期参数:

 // Read file data FileStream fs = new FileStream("c:\\people.doc", FileMode.Open, FileAccess.Read); byte[] data = new byte[fs.Length]; fs.Read(data, 0, data.Length); fs.Close(); // Generate post objects Dictionary<string, object> postParameters = new Dictionary<string, object>(); postParameters.Add("filename", "People.doc"); postParameters.Add("fileformat", "doc"); postParameters.Add("file", new FormUpload.FileParameter(data, "People.doc", "application/msword")); // Create request and receive response string postURL = "http://localhost"; string userAgent = "Someone"; HttpWebResponse webResponse = FormUpload.MultipartFormDataPost(postURL, userAgent, postParameters); // Process response StreamReader responseReader = new StreamReader(webResponse.GetResponseStream()); string fullResponse = responseReader.ReadToEnd(); webResponse.Close(); Response.Write(fullResponse); 

使用.NET 4.0,您目前可以使用System.Net.Http命名空间。 下面是使用多部分表单数据上传单个文件的示例。

 using System; using System.IO; using System.Net.Http; namespace HttpClientTest { class Program { static void Main(string[] args) { var client = new HttpClient(); var content = new MultipartFormDataContent(); content.Add(new StreamContent(File.Open("../../Image1.png", FileMode.Open)), "Image", "Image.png"); content.Add(new StringContent("Place string content here"), "Content-Id in the HTTP"); var result = client.PostAsync("https://hostname/api/Account/UploadAvatar", content); Console.WriteLine(result.Result.ToString()); } } } 

基于dnolans的例子,这是我实际上可以工作的版本(边界有一些错误,没有设置编码):-)

发送数据:

 HttpWebRequest oRequest = null; oRequest = (HttpWebRequest)HttpWebRequest.Create("http://you.url.here"); oRequest.ContentType = "multipart/form-data; boundary=" + PostData.boundary; oRequest.Method = "POST"; PostData pData = new PostData(); Encoding encoding = Encoding.UTF8; Stream oStream = null; /* ... set the parameters, read files, etc. IE: pData.Params.Add(new PostDataParam("email", "example@example.com", PostDataParamType.Field)); pData.Params.Add(new PostDataParam("fileupload", "filename.txt", "filecontents" PostDataParamType.File)); */ byte[] buffer = encoding.GetBytes(pData.GetPostData()); oRequest.ContentLength = buffer.Length; oStream = oRequest.GetRequestStream(); oStream.Write(buffer, 0, buffer.Length); oStream.Close(); HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse(); 

PostData类应该如下所示:

 public class PostData { // Change this if you need to, not necessary public static string boundary = "AaB03x"; private List<PostDataParam> m_Params; public List<PostDataParam> Params { get { return m_Params; } set { m_Params = value; } } public PostData() { m_Params = new List<PostDataParam>(); } /// <summary> /// Returns the parameters array formatted for multi-part/form data /// </summary> /// <returns></returns> public string GetPostData() { StringBuilder sb = new StringBuilder(); foreach (PostDataParam p in m_Params) { sb.AppendLine("--" + boundary); if (p.Type == PostDataParamType.File) { sb.AppendLine(string.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", p.Name, p.FileName)); sb.AppendLine("Content-Type: application/octet-stream"); sb.AppendLine(); sb.AppendLine(p.Value); } else { sb.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", p.Name)); sb.AppendLine(); sb.AppendLine(p.Value); } } sb.AppendLine("--" + boundary + "--"); return sb.ToString(); } } public enum PostDataParamType { Field, File } public class PostDataParam { public PostDataParam(string name, string value, PostDataParamType type) { Name = name; Value = value; Type = type; } public PostDataParam(string name, string filename, string value, PostDataParamType type) { Name = name; Value = value; FileName = filename; Type = type; } public string Name; public string FileName; public string Value; public PostDataParamType Type; } 

在我使用.NET的版本中,你也必须这样做:

 System.Net.ServicePointManager.Expect100Continue = false; 

如果你不这样做的话, HttpWebRequest类将自动添加Expect:100-continue请求头部,这会把所有东西都弄脏。

另外,我也学会了一个艰难的道路,那就是你必须有合适的破折号。 不pipe你说什么, Content-Type头中的“边界”都必须以两个破折号开头

 --THEBOUNDARY 

并在最后

 --THEBOUNDARY-- 

就像它在示例代码中一样。 如果你的边界是很多破折号,然后是一个数字,那么通过查看代理服务器中的http请求,这个错误并不明显

感谢代码,它为我节省了很多时间(包括Except100错误!)。

无论如何,我在代码中发现一个错误,在这里:

 formDataStream.Write(encoding.GetBytes(postData), 0, postData.Length); 

如果你的POST数据是utf-16,postData.Length将返回字符数而不是字节数。 这将截断发布的数据(例如,如果你有2个字符编码为utf-16,他们需要4个字节,但postData.Length会说它需要2个字节,你松了发布的最后2个字节数据)。

解决方法 – 用下面的代码replace该行

 byte[] aPostData=encoding.GetBytes(postData); formDataStream.Write(aPostData, 0, aPostData.Length); 

使用这个,长度是通过字节[]的大小来计算的,而不是string的大小。

上课之前稍微优化一下。 在这个版本中,这些文件并没有完全加载到内存中。

安全build议:检查边界是否丢失,如果文件包含界限将会崩溃。

 namespace WindowsFormsApplication1 { public static class FormUpload { private static string NewDataBoundary() { Random rnd = new Random(); string formDataBoundary = ""; while (formDataBoundary.Length < 15) { formDataBoundary = formDataBoundary + rnd.Next(); } formDataBoundary = formDataBoundary.Substring(0, 15); formDataBoundary = "-----------------------------" + formDataBoundary; return formDataBoundary; } public static HttpWebResponse MultipartFormDataPost(string postUrl, IEnumerable<Cookie> cookies, Dictionary<string, string> postParameters) { string boundary = NewDataBoundary(); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(postUrl); // Set up the request properties request.Method = "POST"; request.ContentType = "multipart/form-data; boundary=" + boundary; request.UserAgent = "PhasDocAgent 1.0"; request.CookieContainer = new CookieContainer(); foreach (var cookie in cookies) { request.CookieContainer.Add(cookie); } #region WRITING STREAM using (Stream formDataStream = request.GetRequestStream()) { foreach (var param in postParameters) { if (param.Value.StartsWith("file://")) { string filepath = param.Value.Substring(7); // Add just the first part of this param, since we will write the file data directly to the Stream string header = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\";\r\nContent-Type: {3}\r\n\r\n", boundary, param.Key, Path.GetFileName(filepath) ?? param.Key, MimeTypes.GetMime(filepath)); formDataStream.Write(Encoding.UTF8.GetBytes(header), 0, header.Length); // Write the file data directly to the Stream, rather than serializing it to a string. byte[] buffer = new byte[2048]; FileStream fs = new FileStream(filepath, FileMode.Open); for (int i = 0; i < fs.Length; ) { int k = fs.Read(buffer, 0, buffer.Length); if (k > 0) { formDataStream.Write(buffer, 0, k); } i = i + k; } fs.Close(); } else { string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n", boundary, param.Key, param.Value); formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, postData.Length); } } // Add the end of the request byte[] footer = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n"); formDataStream.Write(footer, 0, footer.Length); request.ContentLength = formDataStream.Length; formDataStream.Close(); } #endregion return request.GetResponse() as HttpWebResponse; } } } 

我需要模拟一个浏览器login到一个网站获得一个logincookie,login表单是multipart / form-data。

我从这里的其他答案中得到了一些线索,然后试图让我自己的场景工作。 在它正常工作之前,它花了一些令人沮丧的试验和错误,但是这里是代码:

  public static class WebHelpers { /// <summary> /// Post the data as a multipart form /// </summary> public static HttpWebResponse MultipartFormDataPost(string postUrl, string userAgent, Dictionary<string, string> values) { string formDataBoundary = "---------------------------" + WebHelpers.RandomHexDigits(12); string contentType = "multipart/form-data; boundary=" + formDataBoundary; string formData = WebHelpers.MakeMultipartForm(values, formDataBoundary); return WebHelpers.PostForm(postUrl, userAgent, contentType, formData); } /// <summary> /// Post a form /// </summary> public static HttpWebResponse PostForm(string postUrl, string userAgent, string contentType, string formData) { HttpWebRequest request = WebRequest.Create(postUrl) as HttpWebRequest; if (request == null) { throw new NullReferenceException("request is not a http request"); } // Add these, as we're doing a POST request.Method = "POST"; request.ContentType = contentType; request.UserAgent = userAgent; request.CookieContainer = new CookieContainer(); // We need to count how many bytes we're sending. byte[] postBytes = Encoding.UTF8.GetBytes(formData); request.ContentLength = postBytes.Length; using (Stream requestStream = request.GetRequestStream()) { // Push it out there requestStream.Write(postBytes, 0, postBytes.Length); requestStream.Close(); } return request.GetResponse() as HttpWebResponse; } /// <summary> /// Generate random hex digits /// </summary> public static string RandomHexDigits(int count) { Random random = new Random(); StringBuilder result = new StringBuilder(); for (int i = 0; i < count; i++) { int digit = random.Next(16); result.AppendFormat("{0:x}", digit); } return result.ToString(); } /// <summary> /// Turn the key and value pairs into a multipart form /// </summary> private static string MakeMultipartForm(Dictionary<string, string> values, string boundary) { StringBuilder sb = new StringBuilder(); foreach (var pair in values) { sb.AppendFormat("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n", boundary, pair.Key, pair.Value); } sb.AppendFormat("--{0}--\r\n", boundary); return sb.ToString(); } } } 

它不处理文件数据,只是forms,因为这是我所需要的。 我这样打电话:

  try { using (HttpWebResponse response = WebHelpers.MultipartFormDataPost(postUrl, UserAgentString, this.loginForm)) { if (response != null) { Cookie loginCookie = response.Cookies["logincookie"]; ..... 

以下是我正在使用的代码

  //This URL not exist, it's only an example. string url = "http://myBox.s3.amazonaws.com/"; //Instantiate new CustomWebRequest class CustomWebRequest wr = new CustomWebRequest(url); //Set values for parameters wr.ParamsCollection.Add(new ParamsStruct("key", "${filename}")); wr.ParamsCollection.Add(new ParamsStruct("acl", "public-read")); wr.ParamsCollection.Add(new ParamsStruct("success_action_redirect", "http://www.yahoo.com")); wr.ParamsCollection.Add(new ParamsStruct("x-amz-meta-uuid", "14365123651274")); wr.ParamsCollection.Add(new ParamsStruct("x-amz-meta-tag", "")); wr.ParamsCollection.Add(new ParamsStruct("AWSAccessKeyId", "zzzz")); wr.ParamsCollection.Add(new ParamsStruct("Policy", "adsfadsf")); wr.ParamsCollection.Add(new ParamsStruct("Signature", "hH6lK6cA=")); //For file type, send the inputstream of selected file StreamReader sr = new StreamReader(@"file.txt"); wr.ParamsCollection.Add(new ParamsStruct("file", sr, ParamsStruct.ParamType.File, "file.txt")); wr.PostData(); 

从下面的链接我已经下载了相同的代码http://www.codeproject.com/KB/cs/multipart_request_C_.aspx

任何帮助

我的实现

 /// <summary> /// Sending file via multipart\form-data /// </summary> /// <param name="url">URL for send</param> /// <param name="file">Local file path</param> /// <param name="paramName">Request file param</param> /// <param name="contentType">Content-Type file headr</param> /// <param name="nvc">Additional post params</param> private static string httpUploadFile(string url, string file, string paramName, string contentType, NameValueCollection nvc) { //delimeter var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); //creating request var wr = (HttpWebRequest)WebRequest.Create(url); wr.ContentType = "multipart/form-data; boundary=" + boundary; wr.Method = "POST"; wr.KeepAlive = true; //sending request using(var requestStream = wr.GetRequestStream()) { using (var requestWriter = new StreamWriter(requestStream, Encoding.UTF8)) { //params const string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}"; foreach (string key in nvc.Keys) { requestWriter.Write(boundary); requestWriter.Write(String.Format(formdataTemplate, key, nvc[key])); } requestWriter.Write(boundary); //file header const string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n"; requestWriter.Write(String.Format(headerTemplate, paramName, file, contentType)); //file content using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) { fileStream.CopyTo(requestStream); } requestWriter.Write("\r\n--" + boundary + "--\r\n"); } } //reading response try { using (var wresp = (HttpWebResponse)wr.GetResponse()) { if (wresp.StatusCode == HttpStatusCode.OK) { using (var responseStream = wresp.GetResponseStream()) { if (responseStream == null) return null; using (var responseReader = new StreamReader(responseStream)) { return responseReader.ReadToEnd(); } } } throw new ApplicationException("Error while upload files. Server status code: " + wresp.StatusCode.ToString()); } } catch (Exception ex) { throw new ApplicationException("Error while uploading file", ex); } }