Multipage forms from C # client

I am trying to fill out a form in a php application from a C # client (Outlook addin). I used Fiddler to see the original request from a php application, and the form is passed as multipart / form. Unfortunately .Net does not have built-in support for this type of form (WebClient only has a way to upload a file). Does anyone know a library or have some kind of code for this? I want to publish various values ​​and optionally (but only sometimes) a file.

Thanks for your help, Sebastian

+51
c # multipartform-data
Oct 20 '08 at 20:38
source share
10 answers

This is cut and pasted from some sample code that I wrote, hope it should give the basics. It only supports file data and form data at the moment.

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; } 

To send data, you need to:

 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(); 

Hope this is clear, I cut and pasted from several sources to get it more flavorful.

+34
Oct 20 '08 at 21:29
source share
β€” -

Thanks for the answers, everyone! I recently had to get this to work, and used your suggestions heavily. However, there were several complex parts that did not work as expected, mainly related to the actual inclusion of the file (which was an important part of the question). There are already many answers here, but I think this may be useful to someone in the future (I could not find many clear examples of this online). I wrote a blog post that explains this a bit more.

Basically, I first tried to transfer the file data as a UTF8 encoded string, but I had problems with the encoding files (it worked fine for a regular text file, but when loading a Word Document, for example, if I tried to save the file that was transferred to the published the form using Request.Files [0]. SaveAs () didn’t open the file in Word. I found that if you write the file data directly using Stream (most likely than StringBuilder), it worked as expected. Besides In addition, I made a couple of modifications that made my understanding easier.

By the way, the Multilateral Request for Forms for Comments and W3C Recommendation for mulitpart / form-data are some useful resources if someone needs a link for the specification.

I changed the WebHelpers class a little smaller and had simpler interfaces, now it is called FormUpload . If you pass FormUpload.FileParameter , you can pass the contents of the byte [] along with the file name and content type, and if you pass the string, it will treat it as a standard name / value combination.

Here is the FormUpload class:

 // 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; } } } 

Here is the call code that downloads the file and a few common message options:

 // 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); 
+69
Apr 20 '09 at 16:40
source share

With .NET 4.5, you can currently use the System.Net.Http namespace. The following is an example of loading a single file using data in multiple formats.

 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()); } } } 
+28
Aug 14 '13 at 13:45
source share

Based on the dnolans example, this is the version I could really work on (there were errors with the border, the encoding was not installed) :-)

To send data:

 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(); 

The PostData class should look like this:

 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; } 
+13
Dec 11 '08 at
source share

In the .NET version I'm using, you should also do this:

 System.Net.ServicePointManager.Expect100Continue = false; 

If you do not, the HttpWebRequest class HttpWebRequest automatically add the Expect:100-continue request header, which will foul everything.

I also found out that you need to have the right amount of dashes. no matter what you say, the β€œborder” in the Content-Type header should precede two strokes

 --THEBOUNDARY 

and in the end

 --THEBOUNDARY-- 

exactly the same as in the code example. If there are a lot of hyphens on your border followed by a number, then this error will not be obvious if you look at the http request in the proxy server

+9
Jun 24 '09 at 19:31
source share

Thanks for the code, it has a lot of me (including the Except100 error!).

Anyway, I found an error in the code, here:

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

In case your POST data is utf-16, postData.Length will return the number of characters, not the number of bytes. This will truncate the published data (for example, if you have 2 characters encoded as utf-16, they take 4 bytes, but postData.Length will say that it takes 2 bytes and you lose the last two bytes of the published data).

Solution - replace this line as follows:

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

Using this, the length is calculated by the size of the byte [], not the size of the string.

+3
Jul 09 '10 at 10:16
source share

Minor class optimization before. In this version, files are not fully loaded into memory.

Security tip: there is no border check; if the file contains a restriction, it will fail.

 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; } } } 
+2
Dec 04 '09 at 1:10
source share

I needed to simulate a browser login to a website in order to get a cookie to log in, and the login form is multipart / form-data.

I took some hints from the other answers here, and then tried to get my own script to work. Before this worked correctly, it took a little disappointment with trial and error, but here is the code:

  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(); } } } 

It does not process the file data, just a form, as all I need. I called it:

  try { using (HttpWebResponse response = WebHelpers.MultipartFormDataPost(postUrl, UserAgentString, this.loginForm)) { if (response != null) { Cookie loginCookie = response.Cookies["logincookie"]; ..... 
0
Feb 08 '09 at 19:58
source share

Below is the code I'm using

  //This URL not exist, it 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(); 

from the following link I downloaded the same code http://www.codeproject.com/KB/cs/multipart_request_C_.aspx

Any help

0
Jun 04 '09 at 13:31
source share

My implementation

 /// <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); } } 
0
Sep 17 '12 at 12:14
source share



All Articles