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.
// 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, , 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"), , 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), , encoding.GetByteCount(header));
// Write the file data directly to the Stream, rather than serializing it to a string.
formDataStream.Write(fileToUpload.File, , 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), , 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), , encoding.GetByteCount(footer));
// Dump the Stream into a byte[]
formDataStream.Position = ;
byte[] formData = new byte[formDataStream.Length];
formDataStream.Read(formData, , 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, , 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 31st, 2011 at 6:42 pm
Hi Brian,
Would this work for uploads from a client machine to a remote server? I’ve been breaking my head trying to figure out how to make that happen. I’m building a c# web app. I need to upload the files in chunks, and it needs to be transmitted over http (uploading from client to remote cloud server via api). Using a fileupload won’t work, since these files are large (~ 10GB) and the delay between processing the file and hitting the server is way too long. I need to allow the user to type in a file location, hit upload, and stream the file in bytes one chunk at a time.
Any suggestions??
Thank you.
June 4th, 2011 at 7:54 am
belle and fabiim,
For these situations, you could check out RestSharp: https://github.com/johnsheehan/RestSharp. Its file uploading was based off of this method, but has been extended with asynchronous support (see the ExecuteAsync function), and it might be what you are looking for.
July 22nd, 2011 at 3:28 am
Hi, i tried the code mentioned here and its not working for me
i got the error or exception
{“The underlying connection was closed: An unexpected error occurred on a receive.”}
{“Unable to read data from the transport connection: An established connection was aborted by the software in your host machine.”}
at the line ” return request.GetResponse() as HttpWebResponse;
I disable antivirus, firewall on my system and still i rxv same error. Any method to solve this error
Thanks
July 25th, 2011 at 11:09 pm
Hi now i am able to avoid the error
{“The underlying connection was closed: An unexpected error occurred on a receive.”}
{“Unable to read data from the transport connection: An established connection was aborted by the software in your host machine.”}
by properly arrange the header values.(by examine the request via fiddler tool)
But now i am facing this error
The remote server returned an error: (502) Bad Gateway. error , whats the reason?
July 27th, 2011 at 3:46 am
Excellent and Thank you !
August 23rd, 2011 at 10:18 pm
Hi there, I found your blog via Google at the same time as looking for a related matter, your site got here up, it looks good. I’ve bookmarked it in my google bookmarks.
August 30th, 2011 at 8:26 am
Thanks Brian! After two full days of grinding on this myself I found your post and all was solved. Thanks Again!
October 27th, 2011 at 4:39 am
Please use something like
instead of a hard-coded boundary. For example, it would now be impossible to upload this very page using this code, because the data would contain the boundary. Having a random boundary makes the chance of that happening near-infinitely small.
December 11th, 2011 at 7:11 am
really useful Here’s some mildly amuzing stuff I found: Thought for the day? : I went to the museum where they had all the heads and arms from the statues that are in all the other museums.
December 30th, 2011 at 2:11 am
I savour, lead to I discovered just what I used to be taking a look for. You have ended my 4 day long hunt! God Bless you man. Have a nice day. Bye
January 3rd, 2012 at 4:23 pm
I don’t even know how I ended up here, but I thought this post was good. I don’t know who you’re but certainly you are going to a famous blogger in case you aren’t already Cheers!… Heya i’m for the very first time here. I found this board and I uncover It really helpful & it helped me out a lot. I hope to give something back and aid other people like you helped me….
January 18th, 2012 at 1:45 am
I have searching for solution about three days, the Internet full of codes which show how to post file and additional data using c#. The difference between Your code and others which I found is that Your code actually works. The internet full of junk and it takes a time to find gems :) Thank you Brian!
February 8th, 2012 at 9:27 am
The code is great, thank you, but I had trouble running it until I applied Pykaso’s fix with the removal of the redundant \r\n before the footer, even though in my form I posted a single file.
The weired thing was that it used to work for me perfectly even without this fix, but then I added some extra parameters to the form and suddenly begun receiving error 500 internal server error.
I found out that the problem was that the last parameter was not a file, and therefore just before the footer this was appended: “–{0}\r\nContent-Disposition: form-data; name=\”{1}\”\r\n\r\n{2}\r\n”
which means an extra \r\n before the footer, and that was my problem! hope it helps…
February 9th, 2012 at 11:26 am
GREAT POST!
Pykaso fix for UTF-8 text worked for me.
February 10th, 2012 at 1:09 am
Dear developer, are you aware of the Unicode characters bug?
The line
formDataStream.Write(Encoding.GetBytes(postData), 0, postData.Length);
is buggy for Unicode characters. Encoding.GetBytes is used to read bytes and postData.Length for length. The length will be different for unicode characters. As Pykaso correctly pointed the Encoding.GetByteCount method should be used for Unicode characters count.
February 10th, 2012 at 7:04 am
Tomas,
I will update the code now. I was a little worried about making changes that I hadn’t tested but since so many people are mentioning it I changed it. I used .GetByteCount() in 4 different places (“\r\n”, postData, header, and footer). Can you confirm that it is working for you now?
Thanks,
Brian
February 10th, 2012 at 8:39 am
@Gerben Vos
Good idea with this boundary:
I have updated the code.
February 10th, 2012 at 8:43 am
@Pykaso @Eran
I have updated the code to ensure that the extra new line does not get appended at the last field:
I only add the extra newline at the beginning of each parameter that isn’t the first (I removed the duplicate logic from both file and normal key/value POST. I have tested this locally and confirmed that the extra newline is not sent, but if anyone who was having the issue can confirm that it is fixed for them using the new code, that would be great.
February 10th, 2012 at 8:44 am
@Stephen
Thanks for the authentication code sample! I have added it in a comment in the code on the post in case anyone else wants to use it.
April 10th, 2012 at 4:29 pm
Thanks a lot. This helped me a lot.
April 12th, 2012 at 6:54 am
compiling the code in C# 4 I get an error CS0103: The name ‘Response’ does not exist in the current context.
I’ve made the class a library, and used the library in the main code sections.
It seems this line is the line that is giving a compile time error.
Response.Write(fullResponse);
May 11th, 2012 at 7:31 pm
Derek,
You probably need to run it like this instead of
Response
directly.May 21st, 2012 at 9:50 pm
Awesome website…
[…]the time to read or visit the content or sites we have linked to below the[…]……
August 28th, 2012 at 3:21 am
Excellent post. Good job!
October 24th, 2012 at 7:25 am
There is big problem with implementation. The FileParameter object hold Bytes array of file content. If the FormUpload will be used to upload large files and from diferrent thread the chance to get OutOfMemory is very big.
Also Large Object Heap will suffer from such implementation.
November 9th, 2012 at 12:57 pm
Hi Brian,
This looks exactly what I need, but I cannot get it working. I am trying to convert from a cUrl example to c#.
The cUrl example is as follows:
curl -F “[email protected]” “https://api.mogreet.com/cm/media.upload?client_id=849&token=e15b9a70b18c38fa275496836eab1cb0&type=video&name=VideoPostFromCurl”
(I am sending a jpeg instead)
This is my sample call
private void UploadImage2()
{
OpenFileDialog opendlg = new OpenFileDialog();
if (opendlg.ShowDialog() == DialogResult.OK)
{
// Read file data
FileStream fs = new FileStream(opendlg.FileName, 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”, opendlg.FileName);
postParameters.Add(“fileformat”, “jpg”);
postParameters.Add(“file”, new FormUpload.FileParameter(data, Path.GetFileName(opendlg.FileName), “image/jpeg”));
// Create request and receive response
string strRequest = String.Format(“{0}{1}?client_id={2}&token={3}&type={4}&name={5}”,
_urlPrefix,
“cm/media.upload”,
_clientId,
_token,
“image”,
tbImageName.Text
);
string postURL = strRequest;
string userAgent = “Someone”;
HttpWebResponse webResponse = FormUpload.MultipartFormDataPost(postURL, userAgent, postParameters);
// Process response
StreamReader responseReader = new StreamReader(webResponse.GetResponseStream());
string fullResponse = responseReader.ReadToEnd();
tbResponse.Text += “\r\n\r\n” + fullResponse;
webResponse.Close();
}
}
and this is the response
I am not sure what I am missing – any help appreciated
Thansk
Steve Griffiths
November 9th, 2012 at 1:08 pm
Hi Brian,
My aplogies for formatting the previous comment – this is better
This looks exactly what I need, but I cannot get it working. I am trying to convert from a cUrl example to c#.
The cUrl example is as follows:
curl -F “[email protected]″ “https://api.mogreet.com/cm/media.upload?client_id=849&token=e15b9a70b18c38fa275496836eab1cb0&type=video&name=VideoPostFromCurl”
(I am sending a jpeg instead)
This is my sample call
and this is the response
response status=”error” code=”460″
[CDATA[Asset file name You must include an attachment]]
I am not sure what I am missing – any help appreciated
Thansk
Steve Griffiths
April 4th, 2013 at 12:13 pm
anyone have trouble with uploading a MS word document with images embedded?
September 1st, 2013 at 5:46 am
Thank you, thank you, thank you :)
September 1st, 2013 at 8:39 am
Maybe I am doing something wrong, but it does not work if you are redirected, i.e., you POST to http://externalsite.com and the server responds with:
<html> <head> <meta http-equiv=”refresh” content=”0; url=/path/to/somewhere/else”> </head> </html>
October 6th, 2013 at 2:13 am
[…] Multipart Form Post in C# […]
November 12th, 2013 at 5:17 pm
I think this is one of the most important info for me. And
i’m glad reading your article. But should remark on few
general things, The website style is wonderful, the articles is really nice : D.
Good job, cheers
March 5th, 2014 at 4:26 pm
I had to remove the semicolon after the filename and before content-type for it to work. I was using ASP.NET MVC5 and Request.Content.ReadAsMultipartAsync() failed with that semicolon in there.
March 5th, 2014 at 4:32 pm
Dan, thanks for letting me know. I wonder if this changed with a new version? I’m sure this exact code used to work.
March 16th, 2014 at 11:22 pm
Great post, thanks! I ran across an issue using this class to upload a file to http://www.webtranslateit.com API. After getting a lot of 500 responses from the server, I discovered that the semicolumn character ‘;’ in the header variable just before Content-Type had to be removed, and then everything ran smoothly! Not sure if it’s an RFC issue or http://www.webtranslateit.com vendor specific, just reporting it in case it helps someone.
March 17th, 2014 at 6:16 am
Sounds like this is a common problem now – I’ve updated the post to remove the semicolon between filename and content type
March 26th, 2014 at 4:46 am
Multipart-form data
Hi Brain, You have done a great job. At present i am running across an issue.. I want to upload multiple (jpeg,png) images with multipart/form-data as content type in HTTPWEBREQUEST . To describe exactly the issue, i need to upload images path and also i have to read the images in junk characters. how can i convert it to junk characters? Example of junk characters like : ÿØÿà(¢Q@Q@Q@Q@ê(´S¨ Ó¨¢€N¢Š .. I am able to convert it to a byte file and convert ToBase64String but still want to post the data using the junk characters.. The problems are listed below:
1. The path of the image to be uploaded should be given
2. The image path specified should be read and converted into junk characters. (gzip,deflate)
Any help would be appreciated .. thanks.
March 28th, 2014 at 5:04 am
I like the valuable info you provide in your articles.
I’ll bookmark your blog and check again here regularly.
I am quite sure I will learn plenty of new stuff right here!
Best of luck for the next!
March 29th, 2014 at 1:22 am
Hi, Brian. I have a problem. I cant get the code to work more than 2 times in a row.
Basically this wont work :
The output is:
0
1
And ‘2’ never prints. It gets stuck after this comment – // Send the form data to the request.
No exception, no crash… just sits there… like in a infinite circle or something, eventually I get [WebException – The operation has timed out]
March 29th, 2014 at 1:54 am
Oh, I’m really sorry. I wasn’t closing the HttpWebResponse. Sorry for the pointless post.
Here is my version of the code, if someone is interested :
IRequestParameter.cs – http://pastebin.com/MiqFHMuk
StringRequestParameter.cs – http://pastebin.com/XRFPjHyh
FileRequestParameter.cs – http://pastebin.com/EYEGmRbW
MultiformRequest.cs – http://pastebin.com/fn7r10hk
March 31st, 2014 at 6:16 am
MultipartFormDataPost
Hi friends,
Could someone help me on this issue?????
How to concat the string and byte????? My problem is: I have 4images to upload in a website. When i read the image it is converted to byte.. Now between the images , i want to send the string in the post method… Could anyone help me in this issue…. Its important….
Example of string i should post……
Images are converted to byte…..
String1 + Image1 + String2 + Image2 + String3 + Image3 + String4 + Image4 + String5
Please somebody help….
March 31st, 2014 at 9:53 am
Here it is, SurferNetDiya : http://pastebin.com/UCXdGygM
And don’t forget to close the HttpWebResponse after you use it. :D
April 3rd, 2014 at 11:08 pm
Hi Scary
Thanks for your reply…
http://www.tech-archive.net/Archive/DotNet/microsoft.public.dotnet.languages.csharp/2008-02/msg03264.html
Please check the above link.. It is simple and easy too.. But now I am struggling with formation of post content’s
When i convert the whole data into byte format and post the content , there occurs duplicate of string’s…
The link you provided only consists of Image Uploading… I need as follows… Let me post the post content of mt data
// Text in the String format
contents.AppendLine(header);
contents.AppendLine(“Content-Disposition: form-data; name=\”text\””);
contents.AppendLine();
contents.AppendLine(text);
// Text in the String format
contents.AppendLine(header);
contents.AppendLine(“Content-Disposition: form-data; name=\”text\””);
contents.AppendLine();
contents.AppendLine(text);
// Text in the String format
contents.AppendLine(header);
contents.AppendLine(“Content-Disposition: form-data; name=\”text\””);
contents.AppendLine();
contents.AppendLine(text);
// Image when i read it converts to byte format
contents.AppendLine(header);
contents.AppendLine(string.Format(“Content-Disposition: form-data; name=\”image\”; filename=\”{0}\””, filename));
contents.AppendLine(“Content-Type: image/jpeg”);
contents.AppendLine();
contents.AppendLine(File.ReadAllBytes(@”C:\file1.jpg”), “file1.jpg”, “image/jpeg”);
// Image when i read it converts to byte format
contents.AppendLine(header);
contents.AppendLine(string.Format(“Content-Disposition: form-data; name=\”image\”; filename=\”{0}\””, filename));
contents.AppendLine(“Content-Type: image/jpeg”);
contents.AppendLine();
contents.AppendLine(ile.ReadAllBytes(@”C:\file2.jpg”), “file2.jpg”, “image/jpeg”);
// Image when i read it converts to byte format
contents.AppendLine(header);
contents.AppendLine(string.Format(“Content-Disposition: form-data; name=\”image\”; filename=\”{0}\””, filename));
contents.AppendLine(“Content-Type: image/jpeg”);
contents.AppendLine();
contents.AppendLine(ile.ReadAllBytes(@”C:\file3.jpg”), “file3.jpg”, “image/jpeg”);
// Image when i read it converts to byte format
contents.AppendLine(header);
contents.AppendLine(string.Format(“Content-Disposition: form-data; name=\”image\”; filename=\”{0}\””, filename));
contents.AppendLine(“Content-Type: image/jpeg”);
contents.AppendLine();
contents.AppendLine(ile.ReadAllBytes(@”C:\file4.jpg”), “file4.jpg”, “image/jpeg”);
// Text in the String format
contents.AppendLine(header);
contents.AppendLine(“Content-Disposition: form-data; name=\”text\””);
contents.AppendLine();
contents.AppendLine(text);
// Place in the String format
contents.AppendLine(header);
contents.AppendLine(“Content-Disposition: form-data; name=\”place\””);
contents.AppendLine();
contents.AppendLine(place);
// Footer in the String format
contents.AppendLine(footer);
Now how can i form the request and get the response??? Whether to byte format or string format???? Please anyone help…
I tried with many methods and formats but not able to get the response because of the wrong post request
I have found duplicate string are formed while the above post request is sent to the URL
Anyone help please………..
April 4th, 2014 at 10:39 pm
Hi Everyone
Hi friends, awaiting for your reply.. Please help.. how could i form the post data as above…..
Any help would be appreciated….
April 5th, 2014 at 4:55 am
Sorry for Everyone
I completed the Issue… I got the solution…
Thanks to Scary for spending time on my issue….
Have a good day…. :-)
April 11th, 2014 at 10:41 am
This post is fantastic! This really helped me a lot. The only problem I ran into was adding multiple items of the same item type. I was interfacing with an API that allowed for multiple “Attachment” tags in the form data. The easy work around was to use a List<Tuple> instead of a dictionary. All the code will work almost as-is. The only other change was to change all .Key references to .Item1 and .Value to .Item2 (changing from Dictionary lookup key/value pair to Tuple item1/item2)
Thank you so much for this post. You saved my bacon!!
April 23rd, 2014 at 4:02 am
[…] Grinstead wrote a great article on a Multipart form post in C# implementation, for those who are […]
May 15th, 2014 at 9:21 pm
This is excellent! I typically shy away from using external tools like RestSharp unnecessarily and prefer to use nuggets of gold like this where I can. Thanks!