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.

Update: This post has gotten a great response from all the readers who have taken the time to comment and contribute. I would like to take this opportunity to promote the best REST Client for .NET, RestSharp. John Sheehan has implemented this technique using code from this post, which can be seen on github (just look for WriteMultipartFormData). He has also done a great job implementing other basic REST operations in a fully tested suite. I would recommend reading the rest of the post to figure out what is going on behind the scenes, but you might consider using RestSharp in a production environment. Thanks for reading!

Note: If anyone is interested in this code in Visual Basic, reader Mike Ferreira converted the code into VB.Net in a comment below.

// 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 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.

Tags: , , ,

96 Responses to “Multipart Form Post in C#”

  1. New In Foliotek: Inline File Editing « The Lanit Development Blog Says:

    [...] 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 [...]

  2. Collin Says:

    This helped me alot, thank you!

  3. John Says:

    Thanks for the post! Very helpful.

  4. Amrish Shah Says:

    GR8… no words to describe

  5. RJ Says:

    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.

  6. Brian Says:

    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!

  7. Mark M Says:

    Great and excellent, also no other words to describe

  8. RJ Says:

    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!

  9. Brian Says:

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

  10. RJ Says:

    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.

  11. Mark M Says:

    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

  12. Brian Says:

    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:

    public byte[] File;
    public string FileName;
    public string ContentType;

    I think this should work for you. Let me know how it works out.

  13. Mark M Says:

    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?

  14. Brian Says:

    Mark,
    Instead of “var” use:

    foreach (KeyValuePair<string, object> param in postParameters) {  }

    The var is just a convenient shorthand to avoid having to cast the object to that type.

  15. Mark M Says:

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

  16. Brian Says:

    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?

  17. Mark M Says:

    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

  18. Brian Says:

    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?

  19. Mark M Says:

    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

  20. Brian Says:

    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!

  21. Mark M Says:

    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

  22. JP Toto Says:

    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!

  23. JP Toto Says:

    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!

  24. Joe G Says:

    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:

    formDataStream.Write(encoding.GetBytes("\r\n"), 0, 2);

    to GetMultipartFormData, after the line:

    formDataStream.Write(fileToUpload.File, 0, fileToUpload.File.Length);

    Now, with two FileParameters in the Dictionary, I see both files in the Request.Files collection on the other side.

    Cheers,
    -Joe

  25. SlideShare .NET API Wrapper | Frederik Vig - ASP.NET developer Says:

    [...] 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 [...]

  26. Mike Ferreira Says:

    Excellent example. Needed a VB.Net solution, so I converted to VB… Here is the code is interested.

    Imports System.Text
    Imports System.Net
    Imports System.IO
     
    Public Class FormUpload
     
        Private ReadOnly encoding As Encoding = encoding.UTF8
        Public Function MultipartFormDataPost(ByVal postUrl As String, _
                                              ByVal userAgent As String, _
                                              ByVal postParameters As Dictionary(Of String, Object)) _
                                              As HttpWebResponse
            Dim formDataBoundary As String = "-----------------------------19330813700727"
            Dim contentType As String = "multipart/form-data; boundary=" + formDataBoundary
     
            Dim formData As Byte() = GetMultipartFormData(postParameters, formDataBoundary)
     
            Return PostForm(postUrl, 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.Create(postUrl)
     
                If request Is Nothing Then
                    Throw New NullReferenceException("request is not a http request")
                End If
     
                '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 requestStream As Stream = request.GetRequestStream()
                    'Push it out there
                    requestStream.Write(formData, 0, formData.Length)
                    requestStream.Close()
                End Using
     
                Return request.GetResponse
            Catch ex As Exception
                MsgBox("Exception: " &amp; ex.Message &amp; vbCrLf &amp; "Stack: " &amp; ex.StackTrace)
                Throw ex
                Return Nothing
            End Try
     
        End Function
     
     
        Private Function GetMultipartFormData(ByVal postParameters As Dictionary(Of String, Object), _
                                              ByVal boundary As String) _
                                              As Byte()
     
            Dim formDataStream As Stream = New System.IO.MemoryStream()
            For Each param As KeyValuePair(Of String, Object) In postParameters
     
                If TypeOf (param.Value) Is FileParameter Then
                    Dim fileToUpload As FileParameter = param.Value
                    'Add just the first part of this param, since we will write the file data directly to the Stream
                    Dim header As String = "--" &amp; boundary &amp; vbCrLf &amp; _
                                           "Content-Disposition: form-data; name=""" &amp; param.Key &amp; """; filename=""" &amp; fileToUpload.FileName &amp; """;" &amp; vbCrLf &amp; _
                                           "Content-Type: " &amp; fileToUpload.ContentType &amp; vbCrLf &amp; _
                                           "Content-Transfer-Encoding: binary" &amp; vbCrLf &amp; _
                                           vbCrLf
     
                    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
                    Dim postData As String = "--" &amp; boundary &amp; vbCrLf &amp; _
                                             "Content-Disposition: form-data; name=""" &amp; param.Key &amp; """" &amp; vbCrLf &amp; _
                                             "Content-Type: text/plain; charset=windows-1252" &amp; vbCrLf &amp; _
                                             "Content-Transfer-Encoding: 8bit" &amp; vbCrLf &amp; _
                                             vbCrLf &amp; _
                                             param.Value &amp; vbCrLf
                    formDataStream.Write(encoding.GetBytes(postData), 0, postData.Length)
     
                End If
     
            Next
     
            'Add the end of the request
            Dim footer As String = vbCrLf &amp; "--" &amp; boundary &amp; "--" &amp; vbCrLf
            formDataStream.Write(encoding.GetBytes(footer), 0, footer.Length)
     
            'Dump the Stream into a byte()
            formDataStream.Position = 0
            Dim formData As Byte() = New Byte(formDataStream.Length) {}
            formDataStream.Read(formData, 0, formData.Length)
            formDataStream.Close()
     
            Return formData
     
        End Function
     
     
        Public Class FileParameter
            Private pFile As Byte()
            Private pFileName As String
            Private pContentType As String
     
            Public Property File() As Byte()
                Get
                    Return pFile
                End Get
                Set(ByVal value As Byte())
                    pFile = value
                End Set
            End Property
     
            Public Property FileName() As String
                Get
                    Return pFileName
                End Get
                Set(ByVal value As String)
                    pFileName = value
                End Set
            End Property
     
            Public Property ContentType() As String
                Get
                    Return pContentType
                End Get
                Set(ByVal value As String)
                    pContentType = value
                End Set
            End Property
     
            Sub New(ByVal file As Byte())
                Me.New(file, Nothing)
            End Sub
            Sub New(ByVal file As Byte(), ByVal filename As String)
                Me.New(file, filename, Nothing)
            End Sub
            Sub New(ByVal file As Byte(), ByVal filename As String, ByVal contenttype As String)
                Me.File = file
                Me.FileName = filename
                Me.ContentType = contenttype
            End Sub
     
        End Class
     
    End Class
  27. lee Says:

    Newbie question … would this code also work on a Windows Mobile 6 device ?
    thanks

  28. Brian Says:

    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.

  29. Brian Says:

    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.

  30. Upload a File via WebRequest Using CSharp Says:

    [...] 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. … [...]

  31. John Sheehan Says:

    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?

  32. Pkpo Says:

    I love you !

  33. Corey Says:

    This class worked great for posting a multipart form in c sharp. Thanks!

  34. Mithun Says:

    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.

  35. Turkel Says:

    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?

  36. Matt Says:

    Brian – outstanding! Really helped…

    Matt

  37. Pykaso Says:

    For UTF-8 encoded form data im using:

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

    instead of:

    formDataStream.Write(encoding.GetBytes(postData), 0, postData.Length);
  38. diss3ntive Says:

    I…..Love……Youuuuuu

  39. Stephen Says:

    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

    request.ContentLength = formData.Length;

    like I had to add the following to get it to work for me,

    request.PreAuthenticate = true;
    request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
    request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.Encoding.Default.GetBytes("username" + ":" + "password")));

    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.

  40. Pykaso Says:

    When sending multiple files with this class, last file have extra 2 bytes “\r\n” at end of the file.

    Test for last 2 bytes before appending footer ..

    byte[] lastB = new byte[2];
    formDataStream.Position = formDataStream.Length - 2;
    formDataStream.Read(lastB, 0, 2);
     
    string footer = "";
    if (lastB[0] != 13 || lastB[1] != 10)
    {
       footer += "\r\n";
    }
    // Add the end of the request
    footer = "--" + boundary + "--\r\n";
  41. Derek Says:

    Hi Brian,

    Thank you for this helpful and informative post. There was one minor problem I noticed: Shouldn’t a CRLF be appended after the filedata is written in GetMultipartFormData()? According to the RFC specification “the encapsulation boundary must occur at the beginning of a line.” Sorry to be picky, but I thought it would be help for other people reusing this code ;)

  42. Brian Says:

    Derek,
    Thanks for the feedback. I have seen that in earlier comments, but for some reason yours finally made me take another look at it and see where the mistake is. Is this the fix you had in mind?

    formDataStream.Write(encoding.GetBytes("\r\n"), 0, 2);

    I would also like to point out that RestSharp is an excellent library that uses the functionality from this post. You can see that in action here: https://github.com/johnsheehan/RestSharp/blob/master/RestSharp/Http.Sync.cs#L162. If you need a robust REST Client, it is a great choice.

    Thanks!

  43. Bogdan Says:

    Thanks a lot for this! Had to do something similar for a client and you’ve helped a lot!

  44. Marek Says:

    Brian,

    Thanks very much. i tried to build mine from scratch building the headers, I was close, but not close enough! Your code worked pretty well straight off the bat, just plugged in my values and off I went. Spent a whole day on this before finding yours. Excellent Post!

    THANKS AGAIN!

    Marek

  45. Mochaz Says:

    I have tried the following code, but I do not get test.jpg in wwwroot folder
    What’s wrong with my code?
    Do I need to put html file or other in the wwwroot folder?

    Thanks in advance
    :

    // Read file data
    FileStream fs = new FileStream(“c:\\test.jpg”, 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”, “test.txt”);
    postParameters.Add(“fileformat”, “txt”);
    postParameters.Add(“file”, new FormUpload.FileParameter(data, “test.jpg”, “image/jpeg”));

    // Create request and receive response
    string postURL = “http://localhost”;
    string userAgent = “Someone”;
    HttpWebResponse webResponse = FormUpload.MultipartFormDataPost(postURL, userAgent, postParameters);

  46. Brian Says:

    Mochaz,
    That code will post your test.jpg file to http://localhost. You need to have your script at http://localhost handle this file in some way (like saving it in the wwwroot folder or something else. A script running in http://localhost/Default.aspx should be able to access the Request.Files collection and save it.

    For more information on this, see listing 6: “uploading multiple files to the server” on MSDN here: http://msdn.microsoft.com/en-us/library/aa479405.aspx

  47. Deathtospam Says:

    @Pykaso: Why do you call the encoding.GetByteCount(postData) method instead of using the postData.Length property, when dealing with UTF8 encoding?

  48. Russell Says:

    Brian,

    I’m new to C# and was looking for a solution for sending data/image to a remote PHP script. Your code was very helpful.

    Thank you!

  49. Multipart Form Posts in .NET | objetoa.net Says:

    [...] I added some arbitrary number of POST parameters.After awhile I found Brian Grinstead‘s post about how he had constructed his own class that enabled users to create their own custom-formed [...]

  50. fabiim Says:

    How can i adapt this to wait for the entire file to get trough ?
    My request is timing out . Altough i see the server answer in fidler after the timeout exception being thrown .

  51. belle Says:

    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.

  52. Brian Says:

    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.

  53. jibin Says:

    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

  54. jibin Says:

    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?

  55. Dung Says:

    Excellent and Thank you !

  56. Rheba Charlebois Says:

    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.

  57. Jeff Says:

    Thanks Brian! After two full days of grinding on this myself I found your post and all was solved. Thanks Again!

  58. Gerben Vos Says:

    Please use something like

    String.Format("----------{0:N}", Guid.NewGuid());

    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.

  59. Melodi Darvin Says:

    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.

  60. unmetered bandwidth Says:

    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

  61. california mortgage brokers + email list Says:

    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….

  62. Tomas Says:

    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!

  63. Eran Says:

    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…

  64. vittorio Says:

    GREAT POST!
    Pykaso fix for UTF-8 text worked for me.

  65. Tomas Says:

    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.

  66. Brian Says:

    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

  67. Brian Says:

    @Gerben Vos
    Good idea with this boundary:

    String.Format("----------{0:N}", Guid.NewGuid());

    I have updated the code.

  68. Brian Says:

    @Pykaso @Eran

    I have updated the code to ensure that the extra new line does not get appended at the last field:

    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"));
     
         .....

    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.

  69. Brian Says:

    @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.

  70. Dave Says:

    Thanks a lot. This helped me a lot.

  71. Derek Says:

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

  72. Brian Says:

    Derek,
    You probably need to run it like this instead of Response directly.

    System.Web.HttpContext.Current.Response.Write
  73. MMA T-Shirts Says:

    Awesome website…

    [...]the time to read or visit the content or sites we have linked to below the[...]……

  74. Jan Says:

    Excellent post. Good job!

  75. Tomas Says:

    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.

  76. Steve Griffiths Says:

    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 “file=@myvideo.mp4″ “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

  77. Steve Griffiths Says:

    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 “file=@myvideo.mp4″ “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}&amp;token={3}&amp;type={4}&amp;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();
     
                }
            }
     
            private void UploadImage()
            {
                OpenFileDialog opendlg = new OpenFileDialog();
     
                if (opendlg.ShowDialog() == DialogResult.OK)
                {
     
                    FileStream fs = new FileStream(opendlg.FileName, FileMode.Open, FileAccess.Read);
                    byte[] data = new byte[fs.Length];
                    fs.Read(data, 0, data.Length);
                    fs.Close();
     
                    string strRequest = String.Format("{0}{1}?client_id={2}&amp;token={3}&amp;name={4}&amp;type={5}&amp;File={6}",
                                                        _urlPrefix,
                                                        "cm/media.upload",
                                                        _clientId,
                                                        _token,
                                                        tbImageName.Text,
                                                        "image",
                                                        data
                                                        );
     
     
                    tbResponse.Text = "Request" + "\r\n\r\n" + strRequest + "\r\n\r\n";
                    WebRequest request = WebRequest.Create(strRequest);
     
     
                    // If required by the server, set the credentials.
                    request.Credentials = CredentialCache.DefaultCredentials;
     
                    // Get the response.
                    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
     
                    // Display the status.
                    tbResponse.Text += "Response" + "\r\n\r\n" + response.StatusDescription;
     
                    // Get the stream containing content returned by the server.
                    Stream dataStream = response.GetResponseStream();
     
                    // Open the stream using a StreamReader for easy access.
                    StreamReader reader = new StreamReader(dataStream);
     
                    // Read the content.
                    string responseFromServer = reader.ReadToEnd();
     
                    // Display the content.
     
                    tbResponse.Text += "\r\n\r\n" + responseFromServer;
     
                    // Cleanup the streams and the response.
                    reader.Close();
                    dataStream.Close();
                    response.Close();
                }
            }

    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

  78. chi Says:

    anyone have trouble with uploading a MS word document with images embedded?

  79. SKA Says:

    Thank you, thank you, thank you :)

  80. Test User Says:

    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>

  81. 【教程】模拟登陆之如何分析并用代码模拟上传文件 | 在路上 Says:

    […] Multipart Form Post in C# […]

  82. event planning business Says:

    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

  83. Dan Says:

    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.

  84. Brian Says:

    Dan, thanks for letting me know. I wonder if this changed with a new version? I’m sure this exact code used to work.

  85. Themos Piperakis Says:

    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.

  86. Brian Says:

    Sounds like this is a common problem now – I’ve updated the post to remove the semicolon between filename and content type

  87. SurferNetDiya Says:

    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.

  88. beat around the bush Says:

    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!

  89. scarylabcat Says:

    Hi, Brian. I have a problem. I cant get the code to work more than 2 times in a row.

    Basically this wont work :

    for (int i = 0; i &lt; 3; i++)
                {
     
     
                    HttpWebResponse response = FormUpload.MultipartFormDataPost(&quot;https://startpage.com&quot;,
                        &quot;Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36 OPR/20.0.1387.82&quot;, new Dictionary
                        {
                            {
                                "bla", "bla"
                            }
                        });
                    Console.WriteLine(i);
                }

    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]

  90. scarylabcat Says:

    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

  91. SurferNetDiya Says:

    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….

  92. scarylabcat Says:

    Here it is, SurferNetDiya : http://pastebin.com/UCXdGygM

    And don’t forget to close the HttpWebResponse after you use it. :D

  93. SurferNetDiya Says:

    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………..

  94. SurferNetDiya Says:

    Hi Everyone

    Hi friends, awaiting for your reply.. Please help.. how could i form the post data as above…..
    Any help would be appreciated….

  95. SurferNetDiya Says:

    Sorry for Everyone

    I completed the Issue… I got the solution…
    Thanks to Scary for spending time on my issue….
    Have a good day…. :-)

  96. Matthew Bates Says:

    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!!

Leave a Reply

Posting Code: Use html such as <pre lang='javascript'></pre>. See all supported languages.

*