106

I've been searching and reading around to that and couldn't fine anything really useful.

I'm writing an small C# win app that allows user to send files to a web server, not by FTP, but by HTTP using POST. Think of it like a web form but running on a windows application.

I have my HttpWebRequest object created using something like this

HttpWebRequest req = WebRequest.Create(uri) as HttpWebRequest 

and also set the Method, ContentType and ContentLength properties. But thats the far I can go.

This is my piece of code:

HttpWebRequest req = WebRequest.Create(uri) as HttpWebRequest;
req.KeepAlive = false;
req.Method = "POST";
req.Credentials = new NetworkCredential(user.UserName, user.UserPassword);
req.PreAuthenticate = true;
req.ContentType = file.ContentType;
req.ContentLength = file.Length;
HttpWebResponse response = null;

try
{
    response = req.GetResponse() as HttpWebResponse;
}
catch (Exception e) 
{
}

So my question is basically how can I send a fie (text file, image, audio, etc) with C# via HTTP POST.

Thanks!

Agustin Meriles
  • 4,866
  • 3
  • 29
  • 44
gabitoju
  • 1,196
  • 2
  • 9
  • 10
  • 1
    Please check http://stackoverflow.com/questions/15738847/sending-files-using-http-post-in-c-sharp/15739707#15739707 – Sudha Apr 01 '13 at 08:13
  • I have the same need, 8 years later: I have a site that accepts a file upload, shows some content about it, and allows the user to download a report on it if they choose, but now they want an API, so this approach seemed like the easiest way to idiot-proof the client implementation: they just send me a byte array, and then I handle all the implied user actions on the server in the API call, and return the report file they ultimately want to get back instead of the site experience. Without completely refactoring the site and its report generation process to handle this. – Tim Nov 17 '21 at 18:35
  • @Sudha that question is closed. – Hugh Perkins Feb 10 '23 at 11:45
  • See also https://stackoverflow.com/questions/32184360/post-byte-array-to-web-api-server-using-httpclient – Michael Freidgeim Feb 13 '23 at 09:48

9 Answers9

129

Using .NET 4.5 (or .NET 4.0 by adding the Microsoft.Net.Http package from NuGet) there is an easier way to simulate form requests. Here is an example:

private async Task<System.IO.Stream> Upload(string actionUrl, string paramString, Stream paramFileStream, byte [] paramFileBytes)
{
    HttpContent stringContent = new StringContent(paramString);
    HttpContent fileStreamContent = new StreamContent(paramFileStream);
    HttpContent bytesContent = new ByteArrayContent(paramFileBytes);
    using (var client = new HttpClient())
    using (var formData = new MultipartFormDataContent())
    {
        formData.Add(stringContent, "param1", "param1");
        formData.Add(fileStreamContent, "file1", "file1");
        formData.Add(bytesContent, "file2", "file2");
        var response = await client.PostAsync(actionUrl, formData);
        if (!response.IsSuccessStatusCode)
        {
            return null;
        }
        return await response.Content.ReadAsStreamAsync();
    }
}
Joshcodes
  • 8,513
  • 5
  • 40
  • 47
  • I needed to remove the 3rd parameter in `formData.Add()` when the content was of type `StringContent`, in order for the receiving server script to recognize the post fields. – dAngelov May 04 '14 at 14:41
  • 12
    If possible could show a simple example of calling this method? – Jacobr365 Mar 15 '16 at 15:15
  • 13
    whats paramString parameter ? – eran otzap Jul 26 '16 at 14:54
  • AFAIK (tested), formData.Add with two parameters goes to POST field and with three parameters go to FILES field in my php server – Lee Song Jun 05 '17 at 14:56
  • If the files are coming from some other server, what can be the ways I can get the form data and the files separately in the request content? – inthevortex Dec 19 '17 at 15:03
  • 2
    Thank you, very comprehensive example! @eranotzap the paramString is the **actual value** of the param to send. The third argument to `form.Add` is **optional** and only useful for files. – StockBreak Mar 05 '18 at 09:23
  • @joshcodes can I find one **complete** example of this impliemtation? – vibs2006 Mar 13 '18 at 06:12
  • @vibs2006 in https://github.com/eastfivellc/EastFive.SessionServer/blob/master/CredentialProviders/LightspeedProvider.cs the GetRefreshTokenAsync method demonstrates the usage of HttpClient and MultipartFormDataContent. – Joshcodes Mar 13 '18 at 18:35
  • Be wary of `response.Content.ReadAsStreamAsync().Result` your blocking on async code, a better option would be to make your Upload method `async` – Liam May 21 '18 at 11:08
  • 1
    @Liam, I agree fully. The async code was removed from my 2013 answer to keep things simple. Changing it back to an async method has been on my todo list as most C# developers should be comfortable with it at this point. – Joshcodes May 21 '18 at 21:57
  • is there a way to add a file directly to form? – Ammar Shaukat Jun 12 '18 at 08:11
  • @Ammar, by "add a file directly," do you mean a files stored on the local file system? – Joshcodes Jun 19 '18 at 12:41
  • @Joshcodes a simple text file in our system directory – Ammar Shaukat Jun 20 '18 at 08:08
  • 2
    @Ammar, not that I am aware of, I think you would have to read the file into a stream or byte [] and use StreamContent or ByteArrayContent respectively. – Joshcodes Jun 20 '18 at 14:13
59

To send the raw file only:

using(WebClient client = new WebClient()) {
    client.UploadFile(address, filePath);
}

If you want to emulate a browser form with an <input type="file"/>, then that is harder. See this answer for a multipart/form-data answer.

Community
  • 1
  • 1
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • (you can of course add headers / credentials / etc as normal) – Marc Gravell Jul 15 '09 at 13:49
  • 1
    Thanks, I've used it with something simple and I't worked. Now, as you say, I do need to emulate a browser input file, somethig like this . – gabitoju Jul 15 '09 at 13:51
  • 1
    I have used the above code and got error like : Argument exception was unhandled by the user code : {"URI formats are not supported."}. How can I do this? protected void Page_Load(object sender, EventArgs e) { string address="http:www.testproject.com/SavedFiles"; string filepath=@"D:\test\FileOperations\testfile.txt"; using (WebClient client = new WebClient()) { client.UploadFile(address, filepath); } } – Sudha Mar 28 '13 at 07:16
  • 1
    @Sudha have you tried using an actual web address? `http://www.testproject.com/SavedFiles` - note the `//` – Marc Gravell Mar 28 '13 at 08:04
10

For me client.UploadFile still wrapped the content in a multipart request so I had to do it like this:

using (WebClient client = new WebClient())
{
    client.Headers.Add("Content-Type", "application/octet-stream");
    using (Stream fileStream = File.OpenRead(filePath))
    using (Stream requestStream = client.OpenWrite(new Uri(fileUploadUrl), "POST"))
    {
        fileStream.CopyTo(requestStream);
    }
}
Mariusz Jamro
  • 30,615
  • 24
  • 120
  • 162
Meelis Pruks
  • 109
  • 1
  • 3
6

I had got the same problem and this following code answered perfectly at this problem :

//Identificate separator
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
//Encoding
byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

//Creation and specification of the request
HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(url); //sVal is id for the webService
wr.ContentType = "multipart/form-data; boundary=" + boundary;
wr.Method = "POST";
wr.KeepAlive = true;
wr.Credentials = System.Net.CredentialCache.DefaultCredentials;

string sAuthorization = "login:password";//AUTHENTIFICATION BEGIN
byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(sAuthorization);
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes);
wr.Headers.Add("Authorization: Basic " + returnValue); //AUTHENTIFICATION END
Stream rs = wr.GetRequestStream();


string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}"; //For the POST's format

//Writting of the file
rs.Write(boundarybytes, 0, boundarybytes.Length);
byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(Server.MapPath("questions.pdf"));
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, "file", "questions.pdf", contentType);
byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
rs.Write(headerbytes, 0, headerbytes.Length);

FileStream fileStream = new FileStream(Server.MapPath("questions.pdf"), 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();
rs = null;

WebResponse wresp = null;
try
{
    //Get the response
    wresp = wr.GetResponse();
    Stream stream2 = wresp.GetResponseStream();
    StreamReader reader2 = new StreamReader(stream2);
    string responseData = reader2.ReadToEnd();
}
catch (Exception ex)
{
    string s = ex.Message;
}
finally
{
    if (wresp != null)
    {
        wresp.Close();
        wresp = null;
    }
    wr = null;
}
Thomas BLANCHET
  • 249
  • 3
  • 7
3

You need to write your file to the request stream:

using (var reqStream = req.GetRequestStream()) 
{    
    reqStream.Write( ... ) // write the bytes of the file
}
Agustin Meriles
  • 4,866
  • 3
  • 29
  • 44
Pop Catalin
  • 61,751
  • 23
  • 87
  • 115
2
     public string SendFile(string filePath)
            {
                WebResponse response = null;
                try
                {
                    string sWebAddress = "Https://www.address.com";

                    string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
                    byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
                    HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(sWebAddress);
                    wr.ContentType = "multipart/form-data; boundary=" + boundary;
                    wr.Method = "POST";
                    wr.KeepAlive = true;
                    wr.Credentials = System.Net.CredentialCache.DefaultCredentials;
                    Stream stream = wr.GetRequestStream();
                    string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";

                    stream.Write(boundarybytes, 0, boundarybytes.Length);
                    byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(filePath);
                    stream.Write(formitembytes, 0, formitembytes.Length);
                    stream.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, "file", Path.GetFileName(filePath), Path.GetExtension(filePath));
                    byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
                    stream.Write(headerbytes, 0, headerbytes.Length);

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

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

                    response = wr.GetResponse();
                    Stream responseStream = response.GetResponseStream();
                    StreamReader streamReader = new StreamReader(responseStream);
                    string responseData = streamReader.ReadToEnd();
                    return responseData;
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
                finally
                {
                    if (response != null)
                        response.Close();
                }
            }
Masoud Siahkali
  • 5,100
  • 1
  • 29
  • 18
1

To post files as from byte arrays:

private static string UploadFilesToRemoteUrl(string url, IList<byte[]> files, NameValueCollection nvc) {

        string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");

        var request = (HttpWebRequest) WebRequest.Create(url);
        request.ContentType = "multipart/form-data; boundary=" + boundary;
        request.Method = "POST";
        request.KeepAlive = true;
        var postQueue = new ByteArrayCustomQueue();

        var formdataTemplate = "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"{0}\";\r\n\r\n{1}";

        foreach (string key in nvc.Keys) {
            var formitem = string.Format(formdataTemplate, key, nvc[key]);
            var formitembytes = Encoding.UTF8.GetBytes(formitem);
            postQueue.Write(formitembytes);
        }

        var headerTemplate = "\r\n--" + boundary + "\r\n" +
            "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" + 
            "Content-Type: application/zip\r\n\r\n";

        var i = 0;
        foreach (var file in files) {
            var header = string.Format(headerTemplate, "file" + i, "file" + i + ".zip");
            var headerbytes = Encoding.UTF8.GetBytes(header);
            postQueue.Write(headerbytes);
            postQueue.Write(file);
            i++;
        }

        postQueue.Write(Encoding.UTF8.GetBytes("\r\n--" + boundary + "--"));

        request.ContentLength = postQueue.Length;

        using (var requestStream = request.GetRequestStream()) {
            postQueue.CopyToStream(requestStream);
            requestStream.Close();
        }

        var webResponse2 = request.GetResponse();

        using (var stream2 = webResponse2.GetResponseStream())
        using (var reader2 = new StreamReader(stream2)) {

            var res =  reader2.ReadToEnd();
            webResponse2.Close();
            return res;
        }
    }

public class ByteArrayCustomQueue {

    private LinkedList<byte[]> arrays = new LinkedList<byte[]>();

    /// <summary>
    /// Writes the specified data.
    /// </summary>
    /// <param name="data">The data.</param>
    public void Write(byte[] data) {
        arrays.AddLast(data);
    }

    /// <summary>
    /// Gets the length.
    /// </summary>
    /// <value>
    /// The length.
    /// </value>
    public int Length { get { return arrays.Sum(x => x.Length); } }

    /// <summary>
    /// Copies to stream.
    /// </summary>
    /// <param name="requestStream">The request stream.</param>
    /// <exception cref="System.NotImplementedException"></exception>
    public void CopyToStream(Stream requestStream) {
        foreach (var array in arrays) {
            requestStream.Write(array, 0, array.Length);
        }
    }
}
jjaskulowski
  • 2,524
  • 3
  • 26
  • 36
1

You can do it directly with HttpWebRequest/HttpWebResponse like this.

        string serviceUrl = string.Format("{0}/upload?param={1}", "http://127.0.0.1:8080", HttpUtility.UrlEncode(parameter));
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceUrl);
        request.Method = "POST";
        request.KeepAlive = true;
        
        FileStream file = File.OpenRead(pathToFile);
        request.ContentLength = file.Length;

        file.Seek(0, SeekOrigin.Begin);
        file.CopyTo(request.GetRequestStream());

        HttpWebResponse response = (request.GetResponse() as HttpWebResponse);
        StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
        string responseText = reader.ReadToEnd();
critic
  • 309
  • 2
  • 4
0

Using .NET 4.5 trying to perform form POST file upload. Tried most of the methods above but to no avail. Found the solution here https://www.c-sharpcorner.com/article/upload-any-file-using-http-post-multipart-form-data

But I am not not keen as I do not understand why we still need to deal with such low level programming in these common usages (should be handled nicely by framework)

bLaXjack
  • 780
  • 2
  • 11
  • 23
  • Actually, the address has been changed. The new address is https://learn.microsoft.com/en-us/dotnet/api/system.dynamic.expandoobject?view=net-6.0 – mahdi Dec 17 '21 at 13:10