How to upload file to server with HTTP POST multipart/form-data?

375,814

Solution 1

Here's my final working code. My web service needed one file (POST parameter name was "file") & a string value (POST parameter name was "userid").

/// <summary>
/// Occurs when upload backup application bar button is clicked. Author : Farhan Ghumra
 /// </summary>
private async void btnUploadBackup_Click(object sender, EventArgs e)
{
    var dbFile = await ApplicationData.Current.LocalFolder.GetFileAsync(Util.DBNAME);
    var fileBytes = await GetBytesAsync(dbFile);
    var Params = new Dictionary<string, string> { { "userid", "9" } };
    UploadFilesToServer(new Uri(Util.UPLOAD_BACKUP), Params, Path.GetFileName(dbFile.Path), "application/octet-stream", fileBytes);
}

/// <summary>
/// Creates HTTP POST request & uploads database to server. Author : Farhan Ghumra
/// </summary>
private void UploadFilesToServer(Uri uri, Dictionary<string, string> data, string fileName, string fileContentType, byte[] fileData)
{
    string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
    HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
    httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
    httpWebRequest.Method = "POST";
    httpWebRequest.BeginGetRequestStream((result) =>
    {
        try
        {
            HttpWebRequest request = (HttpWebRequest)result.AsyncState;
            using (Stream requestStream = request.EndGetRequestStream(result))
            {
                WriteMultipartForm(requestStream, boundary, data, fileName, fileContentType, fileData);
            }
            request.BeginGetResponse(a =>
            {
                try
                {
                    var response = request.EndGetResponse(a);
                    var responseStream = response.GetResponseStream();
                    using (var sr = new StreamReader(responseStream))
                    {
                        using (StreamReader streamReader = new StreamReader(response.GetResponseStream()))
                        {
                            string responseString = streamReader.ReadToEnd();
                            //responseString is depend upon your web service.
                            if (responseString == "Success")
                            {
                                MessageBox.Show("Backup stored successfully on server.");
                            }
                            else
                            {
                                MessageBox.Show("Error occurred while uploading backup on server.");
                            } 
                        }
                    }
                }
                catch (Exception)
                {

                }
            }, null);
        }
        catch (Exception)
        {

        }
    }, httpWebRequest);
}

/// <summary>
/// Writes multi part HTTP POST request. Author : Farhan Ghumra
/// </summary>
private void WriteMultipartForm(Stream s, string boundary, Dictionary<string, string> data, string fileName, string fileContentType, byte[] fileData)
{
    /// The first boundary
    byte[] boundarybytes = Encoding.UTF8.GetBytes("--" + boundary + "\r\n");
    /// the last boundary.
    byte[] trailer = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
    /// the form data, properly formatted
    string formdataTemplate = "Content-Dis-data; name=\"{0}\"\r\n\r\n{1}";
    /// the form-data file upload, properly formatted
    string fileheaderTemplate = "Content-Dis-data; name=\"{0}\"; filename=\"{1}\";\r\nContent-Type: {2}\r\n\r\n";

    /// Added to track if we need a CRLF or not.
    bool bNeedsCRLF = false;

    if (data != null)
    {
        foreach (string key in data.Keys)
        {
            /// if we need to drop a CRLF, do that.
            if (bNeedsCRLF)
                WriteToStream(s, "\r\n");

            /// Write the boundary.
            WriteToStream(s, boundarybytes);

            /// Write the key.
            WriteToStream(s, string.Format(formdataTemplate, key, data[key]));
            bNeedsCRLF = true;
        }
    }

    /// If we don't have keys, we don't need a crlf.
    if (bNeedsCRLF)
        WriteToStream(s, "\r\n");

    WriteToStream(s, boundarybytes);
    WriteToStream(s, string.Format(fileheaderTemplate, "file", fileName, fileContentType));
    /// Write the file data to the stream.
    WriteToStream(s, fileData);
    WriteToStream(s, trailer);
}

/// <summary>
/// Writes string to stream. Author : Farhan Ghumra
/// </summary>
private void WriteToStream(Stream s, string txt)
{
    byte[] bytes = Encoding.UTF8.GetBytes(txt);
    s.Write(bytes, 0, bytes.Length);
}

/// <summary>
/// Writes byte array to stream. Author : Farhan Ghumra
/// </summary>
private void WriteToStream(Stream s, byte[] bytes)
{
    s.Write(bytes, 0, bytes.Length);
}

/// <summary>
/// Returns byte array from StorageFile. Author : Farhan Ghumra
/// </summary>
private async Task<byte[]> GetBytesAsync(StorageFile file)
{
    byte[] fileBytes = null;
    using (var stream = await file.OpenReadAsync())
    {
        fileBytes = new byte[stream.Size];
        using (var reader = new DataReader(stream))
        {
            await reader.LoadAsync((uint)stream.Size);
            reader.ReadBytes(fileBytes);
        }
    }

    return fileBytes;
}

I am very much thankful to Darin Rousseau for helping me.

Solution 2

Basic implementation using MultipartFormDataContent :-

HttpClient httpClient = new HttpClient();
MultipartFormDataContent form = new MultipartFormDataContent();

form.Add(new StringContent(username), "username");
form.Add(new StringContent(useremail), "email");
form.Add(new StringContent(password), "password");            
form.Add(new ByteArrayContent(file_bytes, 0, file_bytes.Length), "profile_pic", "hello1.jpg");
HttpResponseMessage response = await httpClient.PostAsync("PostUrl", form);

response.EnsureSuccessStatusCode();
httpClient.Dispose();
string sd = response.Content.ReadAsStringAsync().Result;

Solution 3

This simplistic version also works.

public void UploadMultipart(byte[] file, string filename, string contentType, string url)
{
    var webClient = new WebClient();
    string boundary = "------------------------" + DateTime.Now.Ticks.ToString("x");
    webClient.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
    var fileData = webClient.Encoding.GetString(file);
    var package = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n{3}\r\n--{0}--\r\n", boundary, filename, contentType, fileData);

    var nfile = webClient.Encoding.GetBytes(package);

    byte[] resp = webClient.UploadData(url, "POST", nfile);
}

Add any extra required headers if needed.

Solution 4

I've been playing around a little bit and came up with a simplified, more generic solution:

private static string sendHttpRequest(string url, NameValueCollection values, NameValueCollection files = null)
{
    string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
    // The first boundary
    byte[] boundaryBytes = System.Text.Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
    // The last boundary
    byte[] trailer = System.Text.Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
    // The first time it itereates, we need to make sure it doesn't put too many new paragraphs down or it completely messes up poor webbrick
    byte[] boundaryBytesF = System.Text.Encoding.ASCII.GetBytes("--" + boundary + "\r\n");

    // Create the request and set parameters
    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
    request.ContentType = "multipart/form-data; boundary=" + boundary;
    request.Method = "POST";
    request.KeepAlive = true;
    request.Credentials = System.Net.CredentialCache.DefaultCredentials;

    // Get request stream
    Stream requestStream = request.GetRequestStream();

    foreach (string key in values.Keys)
    {
        // Write item to stream
        byte[] formItemBytes = System.Text.Encoding.UTF8.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\";\r\n\r\n{1}", key, values[key]));
        requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
        requestStream.Write(formItemBytes, 0, formItemBytes.Length);
    }

    if (files != null)
    { 
        foreach(string key in files.Keys)
        {
            if(File.Exists(files[key]))
            {
                int bytesRead = 0;
                byte[] buffer = new byte[2048];
                byte[] formItemBytes = System.Text.Encoding.UTF8.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n", key, files[key]));
                requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
                requestStream.Write(formItemBytes, 0, formItemBytes.Length);

                using (FileStream fileStream = new FileStream(files[key], FileMode.Open, FileAccess.Read))
                {
                    while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        // Write file content to stream, byte by byte
                        requestStream.Write(buffer, 0, bytesRead);
                    }

                    fileStream.Close();
                }
            }
        }
    }

    // Write trailer and close stream
    requestStream.Write(trailer, 0, trailer.Length);
    requestStream.Close();

    using (StreamReader reader = new StreamReader(request.GetResponse().GetResponseStream()))
    {
        return reader.ReadToEnd();
    };
}

You can use it like this:

string fileLocation = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + Path.DirectorySeparatorChar + "somefile.jpg";
NameValueCollection values = new NameValueCollection();
NameValueCollection files = new NameValueCollection();
values.Add("firstName", "Alan");
files.Add("profilePicture", fileLocation);
sendHttpRequest("http://example.com/handler.php", values, files);

And in the PHP script you could handle data like this:

echo $_POST['firstName'];
$name = $_POST['firstName'];
$image = $_FILES['profilePicture'];
$ds = DIRECTORY_SEPARATOR;
move_uploaded_file($image['tmp_name'], realpath(dirname(__FILE__)) . $ds . "uploads" . $ds . $image['name']);

Solution 5

You can use this class:

using System.Collections.Specialized;
class Post_File
{
    public static void HttpUploadFile(string url, string file, string paramName, string contentType, NameValueCollection nvc)
    {
        string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
        byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
        byte[] boundarybytesF = System.Text.Encoding.ASCII.GetBytes("--" + boundary + "\r\n");  // the first time it itereates, you need to make sure it doesn't put too many new paragraphs down or it completely messes up poor webbrick.  


        HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(url);
        wr.Method = "POST";
        wr.KeepAlive = true;
        wr.Credentials = System.Net.CredentialCache.DefaultCredentials;
        wr.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
        var nvc2 = new NameValueCollection();
        nvc2.Add("Accepts-Language", "en-us,en;q=0.5");
        wr.Headers.Add(nvc2);
        wr.ContentType = "multipart/form-data; boundary=" + boundary;


        Stream rs = wr.GetRequestStream();

        bool firstLoop = true;
        string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
        foreach (string key in nvc.Keys)
        {
            if (firstLoop)
            {
                rs.Write(boundarybytesF, 0, boundarybytesF.Length);
                firstLoop = false;
            }
            else
            {
                rs.Write(boundarybytes, 0, boundarybytes.Length);
            }
            string formitem = string.Format(formdataTemplate, key, nvc[key]);
            byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem);
            rs.Write(formitembytes, 0, formitembytes.Length);
        }
        rs.Write(boundarybytes, 0, boundarybytes.Length);

        string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
        string header = string.Format(headerTemplate, paramName, new FileInfo(file).Name, contentType);
        byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
        rs.Write(headerbytes, 0, headerbytes.Length);

        FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            rs.Write(buffer, 0, bytesRead);
        }
        fileStream.Close();

        byte[] trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
        rs.Write(trailer, 0, trailer.Length);
        rs.Close();

        WebResponse wresp = null;
        try
        {
            wresp = wr.GetResponse();
            Stream stream2 = wresp.GetResponseStream();
            StreamReader reader2 = new StreamReader(stream2);
        }
        catch (Exception ex)
        {
            if (wresp != null)
            {
                wresp.Close();
                wresp = null;
            }
        }
        finally
        {
            wr = null;
        }
    }
}

use it:

NameValueCollection nvc = new NameValueCollection();
//nvc.Add("id", "TTR");
nvc.Add("table_name", "uploadfile");
nvc.Add("commit", "uploadfile");
Post_File.HttpUploadFile("http://example/upload_file.php", @"C:\user\yourfile.docx", "uploadfile", "application/vnd.ms-excel", nvc);

example server upload_file.php:

m('File upload '.(@copy($_FILES['uploadfile']['tmp_name'],getcwd().'\\'.'/'.$_FILES['uploadfile']['name']) ? 'success' : 'failed'));
function m($msg) {
    echo '<div style="background:#f1f1f1;border:1px solid #ddd;padding:15px;font:14px;text-align:center;font-weight:bold;">';
    echo $msg;
    echo '</div>';
}
Share:
375,814

Related videos on Youtube

Farhan Ghumra
Author by

Farhan Ghumra

Achievements: Microsoft Certified: Azure Developer Associate MCP: Microsoft Certified Professional (Developing ASP.NET MVC Web Applications) 2nd person to earn windows-store-apps bronze badge. 8th person to earn microsoft-metro bronze badge. 10th person to earn windows-runtime bronze badge. 18th person to earn windows-8 bronze badge. Stackoverflow Careers 2.0 : /farhanghumra LinkedIn : /farhanghumra Twitter : @FarhanGhumra Facebook : /farhan.ghumra Skype : farhan.ghumra How to pre-select multiple listview/gridview items in C#/XAML windows 8 app? how to inject RTF file to RichTextBlock in c#/xaml Windows store app How to print the contents of a TextBox in C#/XAML windows 8 app Applying Windows 8 Theme Dashboard to my Website designed in ASP.NET

Updated on July 08, 2022

Comments

  • Farhan Ghumra
    Farhan Ghumra almost 2 years

    I am developing Windows Phone 8 app. I want to upload SQLite database via PHP web service using HTTP POST request with MIME type multipart/form-data & a string data called "userid=SOME_ID".

    I don't want to use 3rd party libs like HttpClient, RestSharp or MyToolkit. I tried the below code but it doesn't upload the file & also doesn't give me any errors. It's working fine in Android, PHP, etc so there's no issue in web service. Below is my given code (for WP8). what's wrong with it?

    I've googled and I'm not getting specific for WP8

    async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(DBNAME);
        //Below line gives me file with 0 bytes, why? Should I use 
        //IsolatedStorageFile instead of StorageFile
        //var file = await ApplicationData.Current.LocalFolder.GetFileAsync(DBNAME);
        byte[] fileBytes = null;
        using (var stream = await file.OpenReadAsync())
        {
            fileBytes = new byte[stream.Size];
            using (var reader = new DataReader(stream))
            {
                await reader.LoadAsync((uint)stream.Size);
                reader.ReadBytes(fileBytes);
            }
        }
    
        //var res = await HttpPost(Util.UPLOAD_BACKUP, fileBytes);
        HttpPost(fileBytes);
    }
    
    private void HttpPost(byte[] file_bytes)
    {
        HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("http://www.myserver.com/upload.php");
        httpWebRequest.ContentType = "multipart/form-data";
        httpWebRequest.Method = "POST";
        var asyncResult = httpWebRequest.BeginGetRequestStream((ar) => { GetRequestStreamCallback(ar, file_bytes); }, httpWebRequest);  
    }
    
    private void GetRequestStreamCallback(IAsyncResult asynchronousResult, byte[] postData)  
    {
        //DON'T KNOW HOW TO PASS "userid=some_user_id"  
        HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;  
        Stream postStream = request.EndGetRequestStream(asynchronousResult);  
        postStream.Write(postData, 0, postData.Length);  
        postStream.Close();  
        var asyncResult = request.BeginGetResponse(new AsyncCallback(GetResponseCallback), request);  
    }  
    
    private void GetResponseCallback(IAsyncResult asynchronousResult)  
    {  
        HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;  
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);  
        Stream streamResponse = response.GetResponseStream();  
        StreamReader streamRead = new StreamReader(streamResponse);  
        string responseString = streamRead.ReadToEnd();  
        streamResponse.Close();  
        streamRead.Close();  
        response.Close();  
    }  
    

    I also tried to solve my problem in Windows 8 but it's also not working.

    public async Task Upload(byte[] fileBytes)
    {
        using (var client = new HttpClient())
        {
            using (var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(System.Globalization.CultureInfo.InvariantCulture)))
            {
                content.Add(new StreamContent(new MemoryStream(fileBytes)));
                //Not sure below line is true or not
                content.Add(new StringContent("userid=farhanW8"));
                using (var message = await client.PostAsync("http://www.myserver.com/upload.php", content))
                {
                    var input = await message.Content.ReadAsStringAsync();
                }
            }
        }
    }
    
  • Farhan Ghumra
    Farhan Ghumra over 10 years
    Thanks, I don't want to depend upon HttpClient, if natively it's supported. HttpClient is available via NuGet in WP8
  • LZH
    LZH almost 10 years
    MultipartFormDataContent is only available in WP8.1
  • Merrimack
    Merrimack about 9 years
    MultipartFormDataContent is available in .NET 4.5 (not just WP)
  • tpartee
    tpartee about 8 years
    Very nice, your code works like a charm. =) This was exactly the kind of solution for both normal form fields and file fields I was looking for. Your code was also easy to expand for multi-file uploads. Thanks!
  • Mike
    Mike about 8 years
    I believe that you send "too many new paragraphs" ("\r\n") at the beginning if nvc is empty.
  • iamatsundere181
    iamatsundere181 about 8 years
    How to perform it in Windows phone 8.1 Rt?
  • Kate
    Kate almost 8 years
    Thank you, Wolf! It realy helps me.
  • John Craft
    John Craft over 6 years
    After wrestling with MultipartFormDataContent unsuccessfully, this approach worked well. It's less glamorous but if you need detailed control over the HTTP message, this is good solution.
  • Admin
    Admin over 6 years
    Millions of thanks to you buddy I spend 3 days solving this and not luck.Thiis is the perfect solution.
  • vibs2006
    vibs2006 about 6 years
    @loop how to add multifiles?
  • Bill Tarbell
    Bill Tarbell over 5 years
    nice job writing the files to the stream a chunk a time instead of loading it all into a byte[] and passing it around.
  • Bill Tarbell
    Bill Tarbell over 5 years
    You can add the files via stream instead of having the entire file content in memory as a byte[]. var fileStream = new FileStream(filePath, FileMode.Open); form.Add(new StreamContent(fileStream), "profile_pic");
  • Mukesh kumar
    Mukesh kumar over 4 years
    byte[] imagebytearraystring = ImageFileToByteArray(imagePath); form.Add(new ByteArrayContent(imagebytearraystring, 0, imagebytearraystring.Count()), "File", filename); --its work file please add this fil
  • Thulasiram
    Thulasiram over 4 years
    Thanks :). Working fine for me. After adding some configuration in web.config file
  • A.Goutam
    A.Goutam over 4 years
    @wolf5 What will be content Type in above example i am trying to send jpg image
  • Wolf5
    Wolf5 over 4 years
  • LastTribunal
    LastTribunal almost 4 years
    The simplest solution is usually the best!
  • LastTribunal
    LastTribunal almost 4 years
    @Bill Tarbell the whole purpose of this exercise is to avoid having to add a file from a a file system, so your suggestion doesn't address the OP
  • Nasreddine Galfout
    Nasreddine Galfout almost 4 years
    Thank you for the MultipartFormDataContent Idea it was what I was looking for.
  • OwnageIsMagic
    OwnageIsMagic over 3 years
    Async().Result is not async
  • cesAR
    cesAR over 3 years
    @OwnageIsMagic I'm unable to understand to which part of the code you refer to. Let's check the order in which the things happen here: 1) PostAsync(...) in the HttpClient() object, which is async. 2) Because of 1), my Post method returns an async Task. 3) Because of 2), I hook my Post(...) method with GetAwaiter().GetResult(), to wait for the asynchronous task to end. What am I missing? Cheers
  • OwnageIsMagic
    OwnageIsMagic over 3 years
    I was referring to streamContent.ReadAsByteArrayAsync().Result. But GetAwaiter().GetResult() is also not async
  • zolty13
    zolty13 about 3 years
    Never call new HttpClient. Yo can destabilize your software aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
  • Junaid Pathan
    Junaid Pathan almost 3 years
    @zolty13, the answerer has used httpClient.Dispose();. Also, It is recommended to instantiate HttpCLient only once. Hence instantiate it in class once and use it whenever required. Instantiate it in class as private static readonly HttpClient client = new HttpClient(); Refer Here-"docs.microsoft.com/en-us/azure/architecture/antipatter‌​ns/…"
  • zolty13
    zolty13 almost 3 years
    Instantiate htrpclient only once results with "DNS issue". Singleton http client doesn't honoured DNS changes. Disposing http client only results in socket with state TIME_WAIT which can long about few minutes. Please read article i linked before
  • AaA
    AaA almost 3 years
    @zolty13, I'm sorry but that article you have linked, unfortunately doesn't know difference between open and established connection and the one that is Time_Wait mode. if you are connecting to same server you are better off instantiating HttpClient because it will keep connection alive and reduce connection time by saving dns resolve time, but if you are connecting to different servers (even same domain name, but with load balancers) it is better to create new instance every time .
  • Rayyan
    Rayyan over 2 years
    only solution which worked for me thank you so much
  • hongxu
    hongxu over 2 years
    This is the cleanest solution I am seeing. The only improvement you can make is to add "using" in front of each of the first three lines.