Multipart Form Post in C#
I recently had to access a web API through C Sharp that required a file upload. This is pretty easy if you have an HTML page with a form tag and you want a user to directly upload the file.
<form method="POST" action="http://localhost/" enctype="multipart/form-data"> File : <input type="file" name="content" size="38" /><br /> <input type="hidden" name="id" value='fileUpload' /> </form>
However, this is not always a reasonable path to take. Sometimes you may be wanting to access a file that is already in a system and you don’t want a new upload. If you are accessing an external API, this is probably always the case. Unfortunately, building this post using C# is not quite as straightforward. I first tried using the WebClient UploadFile method, but it didn’t fit my needs because I wanted to upload form values (id, filename, other API specific parameters) in addition to just a file.
So, I needed to roll my own form post. Here is the Multipart Form RFC and the W3C Specification for multipart/form data. After reading these links and searching some forums, here is what I came up with.
Note: If anyone is interested in this code in Visual Basic, reader Mike Ferreira converted the code into VB.Net in a comment below.
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 = "-----------------------------28947758029299"; 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; // We need to count how many bytes we're sending. using (Stream requestStream = request.GetRequestStream()) { // Push it out there 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(); foreach (var param in postParameters) { 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, header.Length); // 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}\r\n", boundary, param.Key, param.Value); formDataStream.Write(encoding.GetBytes(postData), 0, postData.Length); } } // Add the end of the request string footer = "\r\n--" + boundary + "--\r\n"; formDataStream.Write(encoding.GetBytes(footer), 0, footer.Length); // 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 code to call the MultipartFormDataPost function with multiple parameters, including a file.
// 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);
Hopefully this code can help someone, figuring out exactly where to place the boundary and newlines in between form key-value pairs caused a little bit of grief during development. This is some functionality that would be really nice inside of the language library, but it seems like in most languages this is something you end up coding yourself.
May 19th, 2009 at 6:35 am
[...] did a write up about one of the technical challenges that was encountered when adding this feature: generating a multipart form post in C#. Posted in C#, Development, ePortfolio. Tags: Business, Development, foliotek. No Comments [...]
June 22nd, 2009 at 7:25 am
This helped me alot, thank you!
July 8th, 2009 at 1:35 am
Thanks for the post! Very helpful.
July 9th, 2009 at 11:44 am
GR8… no words to describe
July 20th, 2009 at 3:57 pm
Hi. Great post. I have a quick question about implementing this though. In GetMultipartFormData() -> when processing fileData. Is there a way to have different values for name and filename? right now they are the same value. Also, is there a way to specify the content-type of the file you are uploading? Thanks.
July 21st, 2009 at 6:57 am
RJ, good question – that is something that I didn’t need in my specific case (the server I was uploading to ignored the filename and content-type attributes). It would be useful if you were able to specify those attributes, so I decided to add in that feature.
I updated the code to allow the ability to specify a file name and content-type. Let me know if this works for you!
July 21st, 2009 at 12:09 pm
Great and excellent, also no other words to describe
July 21st, 2009 at 12:17 pm
Hey Brian. Thanks for the quick response :) This works. I did have one issue in the method GetMultipartFormData() when you encode the header and post data. I had to create a local variable called encoding:
Encoding encoding = Encoding.UTF8;
Which allowed this line to work:
formDataStream.Write(encoding.GetBytes(header), 0, header.Length);
I also modified your script to accept a username and password for network credentials in the PostForm() method:
if (strUserName != null && strPassword != null)
request.Credentials = new NetworkCredential(strUserName, strPassword);
I also added a NameValueCollection of possible Cookies as well that get passed into your methods, and in PostForm() I loop through the collection and add them to the CookieContainer:
NameValueCollection objCookies; //passed into method as a parameter
request.CookieContainer = new CookieContainer();
foreach (string strKey in objCookies.AllKeys)
request.CookieContainer.Add(new Cookie(strKey, objCookies[strKey], “/”, “local.website.com”));
I needed to send cookies as a way of authenticating to the server (which is a 3rd party community app running on one of our subdomains)
Thanks!
July 21st, 2009 at 12:33 pm
RJ, thanks for the feedback!
I had forgotten to copy the encoding variable into the code. It is there now – the first line in the class.
Good idea about adding in the authentication and cookie information. What information are you passing with the cookies? Could you share an example of what you would do to generate objCookies before you call PostForm()?
July 21st, 2009 at 2:16 pm
No prob. Like I mentioned earlier, we are using the “form post” to post files to a 3rd party software system running within our domain, so authentication with cookies is valid in our case. Right before I call MultipartFormDataPost(), I create a NameValueCollection of cookies:
NameValueCollection objCookies = null;
objCookies = new NameValueCollection();
objCookies["info] = “uid=johndoe&ts=12999333&apiKey=abcdefg123″;
FormUpload.MultipartFormDataPost(strUrl, null, null, null, objCookies, objPostParameters);
Then from here, look at my previous comment on how to set each cookie to the CookieContainer.
You could, in theory pass in a CookieCollection rather than a NameValueCollection but I just stuck with this approach.
I believe (in our case) having this cookie set and sent allows us to do simple “shared” domain authentication.
I’m not sure how this would work when posting to a server in a different domain.
July 21st, 2009 at 3:26 pm
Hello Brian, May I ask if this code only compiled in asp 3.5? I got error with asp.2.0
complier says ‘FormUpload.FileParameter.File.get’ must declare a body because it is not marked abstract or extern
‘FormUpload.FileParameter.File.set’ must declare a body because it is not marked abstract or extern
Suggestion? Thanks
July 21st, 2009 at 7:34 pm
Hi Mark. The code uses the auto-implemented property feature in C# 3.0 and later.
If you are using an older version, then you should be able to remove them altogether so it would look like:
I think this should work for you. Let me know how it works out.
July 22nd, 2009 at 10:43 am
Thanks Brian, hate to point out again. Below syntax is also available for c# 3.0
foreach (var param in postParameters)
{
if (param.Value is FileParameter)
{
FileParameter fileToUpload = (FileParameter)param.Value;
the var cannot be recognized by the compiler. Will you be able to use another syntax for this purpose?
July 22nd, 2009 at 11:13 am
Mark,
Instead of “var” use:
The var is just a convenient shorthand to avoid having to cast the object to that type.
July 22nd, 2009 at 12:30 pm
THanks a lot and I don’t have compile error now. I am trying to upload with following code. But I got the file field empty. Can you see what should be changed?
// Read file data
FileStream fs = new FileStream(“E:\\default\\aspnet\\Upload\\Racing.flv”, FileMode.Open, FileAccess.Read);
byte[] data = new byte[fs.Length];
fs.Read(data, 0, data.Length);
fs.Close();
// Generate post objects
Dictionary postParameters = new Dictionary();
postParameters.Add(“filename”, “Racing_001.flv”);
postParameters.Add(“userlogin”, “tomcattyy”);
postParameters.Add(“password”, “mark888″);
postParameters.Add(“title”, “customer 23443″);
//postParameters.Add(“fileformat”, “doc”);
postParameters.Add(“file”, new FormUpload.FileParameter(data, “E:\\default\\aspnet\\Upload\\Racing.flv”, “flv-application/octet-stream”));
// Create request and receive response
string postURL = “http://uploads.blip.tv/file/post”;
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);
July 22nd, 2009 at 12:40 pm
Hard to say. Maybe the Filename is causing problems with the post since is has those “\\” in it? Try it with just “Racing.flv”.
What do you mean by “got the file field empty”? Did it work before? Does the rest of the post data go through to your postURL?
July 22nd, 2009 at 1:56 pm
Yes, it takes the user / password / title but file name field is blank, .
I have changed the file name in
postParameters.Add(“file”, new FormUpload.FileParameter(data, “Racing.flv”, “flv-application/octet-stream”));
please take a look at http://208.75.252.245/video_blipup.aspx
Thanks
Mark
July 22nd, 2009 at 2:28 pm
Are you doing uploads from the user’s computer onto your server, or are you uploading a file from your server to another server?
If you are doing uploads from the users’s computer, check out How to upload a file to a Web server in ASP.NET by using Visual C# .NET.
If you are trying to move files from your server to another server, is it only the **file name** that is not coming through, or is the **entire file** not being uploaded?
July 22nd, 2009 at 2:44 pm
I am moving video files from my server to another server. the entire file did not went through.
From the last post link, the file field is blank and I log into blip.tv confirm those files not been upload either. Thanks
July 22nd, 2009 at 3:23 pm
Mark, the interface on that page is a little confusing to me. I’m not sure if I am supposed to be uploading a new video, or how to make it upload the one already on the server.
If you have access to the destination server, maybe you could check what is being received and print out some output to diagnose the problem.
If you don’t have access to the destination server, maybe you could point it to a script you do have access to (change the postURL) and check the Request object to see what is coming through. Specifically, check the Request.Files object to see what is going on with it.
… or maybe it is expecting the parameter name to be something other than “file”?
Let me know what you figure out!
July 22nd, 2009 at 3:54 pm
thanks Brian, please go ahead uploading whatever video to test. I will change the postURL to test.
blip.tv API is located here
http://wiki.blip.tv/index.php/REST_Upload_API
if you want to take a look. they only have a python sample there
July 28th, 2009 at 2:30 pm
This was fantastically helpful, Brian. Thanks so much for publishing this. I’m still studying to see if I can learn the important parts of a multi-part form post. Cheers!
August 15th, 2009 at 11:16 am
Brian,
I took the example you posted here and started a GitHub project based on it where I’m adding some more features and refactoring the code a bit. http://github.com/jptoto/multipart_form_poster/tree/master I give full credit in the README file and a link to your blog post. I hope this ok.
This post REALLY helped me get moving on a project I’m working on. It was a big help for POSTing to an external API. Thanks again.
Cheers!
September 24th, 2009 at 10:43 am
Brian,
In order to work with multiple file uploads, I had to add a CR+LF to the end of each FileParameter stream. In the code, I added the line:
to GetMultipartFormData, after the line:
Now, with two FileParameters in the Dictionary, I see both files in the Request.Files collection on the other side.
Cheers,
-Joe
October 18th, 2009 at 6:32 am
[...] exposed and made available for you to use. Big thanks to Brian Grinstead for his blog post Multipart Form Post in C# and for Gaurav Gupta for creating the wrapper class for the first version, which this code is based [...]
November 5th, 2009 at 8:56 am
Excellent example. Needed a VB.Net solution, so I converted to VB… Here is the code is interested.
November 8th, 2009 at 1:21 pm
Newbie question … would this code also work on a Windows Mobile 6 device ?
thanks
November 9th, 2009 at 7:25 am
Mike,
Thanks for taking the time to post that code after you converted it over to VB.Net. Hopefully it is working for you in your project.
November 9th, 2009 at 7:32 am
Lee,
As far as I know, it should work on Windows Mobile 6. The classes that it uses are mostly in System.Net and System.IO. I’ve never done any Windows Mobile development, so I couldn’t tell you for sure either way.
Take a look at creating your first Windows Mobile 6 Application haven’t gotten the SDK set up yet. Let me know if you try it, and if you needed to make any changes for it to work.
November 10th, 2009 at 7:15 am
[...] Brian Grinstead » Blog Archive » Multipart Form Post in C# – So, I needed to roll my own form post. Here is the Multipart Form RFC and the W3C Specification for multipart/form data. After reading these links and searching some forums, here is what I came up with. ….. userAgent, contentType, formData) End Function Private Function PostForm(ByVal postUrl As String, _ ByVal userAgent As String, _ ByVal contentType As String, _ ByVal formData As Byte()) _ As HttpWebResponse Try Dim request As HttpWebRequest = WebRequest. … [...]
December 1st, 2009 at 6:10 pm
Hi Brian, can you email me in regards to the code in this post? Or can you acknowledge if this code is under any sort of license?
December 14th, 2009 at 9:16 am
I love you !
February 9th, 2010 at 7:21 am
This class worked great for posting a multipart form in c sharp. Thanks!
February 10th, 2010 at 5:24 am
Awsome post!!! Thanks for helping us out of the drudgery…. This saved a lot of time in my work…. May be this should be added as a part of the C# library.
March 6th, 2010 at 1:57 am
Hi Mr Brian
I need your help about that post.
First
I have php file that gets data and file from HTML form and uploads file to specific directory by $_FILES methods..
Second
I have windos aplication (c#).User could drag and drop any file to listview.When he/She clicks send button aplication sends datas of user to php file By POST method and it (php file) upadtes the database.
My problem is I wont to send file in listview drag and dropeed by user to php file as It could get file by $_FILES method and acces it.Sory about my english
thanks.what do I have to do by your post?
April 8th, 2010 at 6:09 pm
Brian – outstanding! Really helped…
Matt
May 10th, 2010 at 6:29 am
For UTF-8 encoded form data im using:
instead of:
June 22nd, 2010 at 7:25 pm
I…..Love……Youuuuuu
August 31st, 2010 at 10:57 am
Absolutely fantastic post. Hve been working on this for the past two days to no avail. I’ve been trying to programatically post files to a unix server and it does not help when when the Java guys
a) Decide to use a form post method for posting files when FTP would adequately do the Job
b) Give you Zero support when their unix server just returns “403″ bad request, apparently they get nothing in the logs
If anyone is interested in using basic authentication with this code,
after the line
like I had to add the following to get it to work for me,
I also wrapped the FileStream and StreamReader object in my caller method within using blocks, which is just decorative/programming style stuff really if you wish.