10

I want to upload file to a host by using WebClient class. I also want to pass some values which should be displayed in the $_POST array on the server part (PHP). I want to do it by one connect

I've used code bellow

using (WebClient wc = new WebClient())
{
    wc.Encoding = Encoding.UTF8;
    NameValueCollection values = new NameValueCollection();
    values.Add("client", "VIP");
    values.Add("name", "John Doe"); 
    wc.QueryString = values; // this displayes in $_GET
    byte[] ans= wc.UploadFile(address, dumpPath);
}

If i've used QueryString property, the values displayed in $_GET array.But i want to send it by post method

gam6itko
  • 15,128
  • 2
  • 19
  • 18
  • Similar question here http://stackoverflow.com/questions/2950292/how-to-upload-multiple-files-using-webclient-uploadfile-uploadvalues-in-c You may want to check the answers – Panagiotis Kanavos Jun 15 '12 at 09:54

2 Answers2

34

There's nothing built-in that allows you to do that. I have blogged about an extension that you could use. Here are the relevant classes:

public class UploadFile
{
    public UploadFile()
    {
        ContentType = "application/octet-stream";
    }
    public string Name { get; set; }
    public string Filename { get; set; }
    public string ContentType { get; set; }
    public Stream Stream { get; set; }
}

public byte[] UploadFiles(string address, IEnumerable<UploadFile> files, NameValueCollection values)
{
    var request = WebRequest.Create(address);
    request.Method = "POST";
    var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
    request.ContentType = "multipart/form-data; boundary=" + boundary;
    boundary = "--" + boundary;

    using (var requestStream = request.GetRequestStream())
    {
        // Write the values
        foreach (string name in values.Keys)
        {
            var buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
            requestStream.Write(buffer, 0, buffer.Length);
            buffer = Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{1}", name, Environment.NewLine));
            requestStream.Write(buffer, 0, buffer.Length);
            buffer = Encoding.UTF8.GetBytes(values[name] + Environment.NewLine);
            requestStream.Write(buffer, 0, buffer.Length);
        }

        // Write the files
        foreach (var file in files)
        {
            var buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
            requestStream.Write(buffer, 0, buffer.Length);
            buffer = Encoding.UTF8.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}", file.Name, file.Filename, Environment.NewLine));
            requestStream.Write(buffer, 0, buffer.Length);
            buffer = Encoding.ASCII.GetBytes(string.Format("Content-Type: {0}{1}{1}", file.ContentType, Environment.NewLine));
            requestStream.Write(buffer, 0, buffer.Length);
            file.Stream.CopyTo(requestStream);
            buffer = Encoding.ASCII.GetBytes(Environment.NewLine);
            requestStream.Write(buffer, 0, buffer.Length);
        }

        var boundaryBuffer = Encoding.ASCII.GetBytes(boundary + "--");
        requestStream.Write(boundaryBuffer, 0, boundaryBuffer.Length);
    }

    using (var response = request.GetResponse())
    using (var responseStream = response.GetResponseStream())
    using (var stream = new MemoryStream())
    {
        responseStream.CopyTo(stream);
        return stream.ToArray();
    }
}

and now you could use it in your application:

using (var stream = File.Open(dumpPath, FileMode.Open))
{
    var files = new[] 
    {
        new UploadFile
        {
            Name = "file",
            Filename = Path.GetFileName(dumpPath),
            ContentType = "text/plain",
            Stream = stream
        }
    };

    var values = new NameValueCollection
    {
        { "client", "VIP" },
        { "name", "John Doe" },
    };

    byte[] result = UploadFiles(address, files, values);
}

Now in your PHP script you could use the $_POST["client"], $_POST["name"] and $_FILES["file"].

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • what would be the steps to make this async with progress indication? – Pbirkoff Sep 18 '13 at 13:26
  • The synchronous functions on the HttpWebClient should be replaced with their asynchronous counterparts: Begin/EndGetRequestStream, Begin/EndGetResponse, ... – Darin Dimitrov Sep 19 '13 at 06:15
  • Thank you for this code! If you are using MVC 4 you may get this exception: 'Unexpected end of MIME multipart stream. MIME multipart message is not complete.' There is a bug inside framework (http://aspnetwebstack.codeplex.com/discussions/354215) and you need to add a new line at the last boundary: var boundaryBuffer = Encoding.ASCII.GetBytes(boundary + "--" + Environment.NewLine); – Radu D Dec 06 '13 at 09:36
  • 2
    In case someone wants to run this on Mono (linux/Mac), you may want to change `Environment.Newline` to explicitly use `\r\n`, since [on those platforms `Environment.Newline` will return `\n`](http://stackoverflow.com/a/1015771/1174169), and [RFC 1867](http://www.faqs.org/rfcs/rfc1867.html) section 5.9 specifies this should always be `CRLF`. Haven't tested this though. Thanks for filling a huge gap in the Web API, @Darin – cod3monk3y Feb 04 '14 at 16:05
  • Why do you switch back and forth between UTF8 and ASCII encoding? For example, for `Content-Disposition` you use ASCII in the values section but UTF8 in the files section. – cod3monk3y Feb 04 '14 at 16:38
  • I still get the following from WebApi 2: "The remote server returned an error: (415) Unsupported Media Type." – flipdoubt Aug 26 '15 at 14:12
  • I'm getting error `400 Bad Request`... How could I adjust this sample in the case for one file? I tried calling UploadFiles with UploadFile list with single file, but got Bad request... Could you suggest what should I change? – Prokurors Sep 22 '16 at 14:30
  • Ok, it seams like that problem was because I had not provided content type - after I added all those fields (actually I provided real data only for fields `Filename`, `ContentType` and `Stream`, i get response from the server. Hurray!!! :) Thanks a lot! – Prokurors Sep 23 '16 at 09:02
  • Sorry for the stupid question, but how do you add more than one file in "files". When i add more than one files in a loop (multiple files uploads with same paramters), i only get the last one on PHP side. If i loop through and upload individual files with same parameters, it works OK. – Shaakir Sep 26 '16 at 15:19
  • 1
    I got 400 Bad Request because of lack of an extra NewLine at the end of the http content.. lol – WTIFS Jan 19 '17 at 05:45
6

If someone wants to use @darin-dimitrov s solution in an async pattern with progress reporting, that's the way to go (for .NET 4.0):

public void UploadFileAsync(NameValueCollection values, Stream fileStream)
{
    //to fire events on the calling thread
    _asyncOperation = AsyncOperationManager.CreateOperation(null);
    var ms = new MemoryStream();
    //make a copy of the input stream in case sb uses disposable stream
    fileStream.CopyTo(ms);
    //you cannot set stream position often enough to zero
    ms.Position = 0;

    Task.Factory.StartNew(() =>
    {
        try
        {
            const string contentType = "application/octet-stream";

            var request = WebRequest.Create(_url);
            request.Method = "POST";
            var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            boundary = "--" + boundary;

            var dataStream = new MemoryStream();
            byte[] buffer;
            // Write the values
            foreach (string name in values.Keys)
            {
                buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
                dataStream.Write(buffer, 0, buffer.Length);
                buffer = Encoding.ASCII.GetBytes(string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{1}", name, Environment.NewLine));
                dataStream.Write(buffer, 0, buffer.Length);
                buffer = Encoding.UTF8.GetBytes(values[name] + Environment.NewLine);
                dataStream.Write(buffer, 0, buffer.Length);
            }

            // Write the file
            buffer = Encoding.ASCII.GetBytes(boundary + Environment.NewLine);
            dataStream.Write(buffer, 0, buffer.Length);
            buffer = Encoding.UTF8.GetBytes($"Content-Disposition: form-data; name=\"file\"; filename=\"file\"{Environment.NewLine}");
            dataStream.Write(buffer, 0, buffer.Length);
            buffer = Encoding.ASCII.GetBytes(string.Format("Content-Type: {0}{1}{1}", contentType, Environment.NewLine));
            dataStream.Write(buffer, 0, buffer.Length);
            ms.CopyTo(dataStream);
            buffer = Encoding.ASCII.GetBytes(Environment.NewLine);
            dataStream.Write(buffer, 0, buffer.Length);

            buffer = Encoding.ASCII.GetBytes(boundary + "--");
            dataStream.Write(buffer, 0, buffer.Length);


            dataStream.Position = 0;
            //IMPORTANT: set content length to directly write to network socket
            request.ContentLength = dataStream.Length;
            var requestStream = request.GetRequestStream();

            //Write data in chunks and report progress
            var size = dataStream.Length;
            const int chunkSize = 64 * 1024;
            buffer = new byte[chunkSize];
            long bytesSent = 0;
            int readBytes;
            while ((readBytes = dataStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                requestStream.Write(buffer, 0, readBytes);
                bytesSent += readBytes;

                var status = "Uploading... " + bytesSent / 1024 + "KB of " + size / 1024 + "KB";
                var percentage = Tools.Clamp(Convert.ToInt32(100 * bytesSent / size), 0, 100);
                OnFileUploaderProgressChanged(new FileUploaderProgessChangedEventArgs(status, percentage));
            }

            //get response
            using (var response = request.GetResponse())
            using (var responseStream = response.GetResponseStream())
            using (var stream = new MemoryStream())
            {
                // ReSharper disable once PossibleNullReferenceException - exception would get catched anyway
                responseStream.CopyTo(stream);
                var result = Encoding.Default.GetString(stream.ToArray());
                OnFileUploaderCompleted(result == string.Empty
                    ? new FileUploaderCompletedEventArgs(FileUploaderCompletedResult.Failed)
                    : new FileUploaderCompletedEventArgs(FileUploaderCompletedResult.Ok));
            }
        }
        catch (Exception)
        {
            OnFileUploaderCompleted(new FileUploaderCompletedEventArgs(FileUploaderCompletedResult.Failed));
        }
    }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
adroste
  • 758
  • 1
  • 7
  • 17
  • Hi, thanks for your code, I'm triyng to implement it in my project but I have some doubt on this part: OnFileUploaderCompleted(result == string.Empty ? new FileUploaderCompletedEventArgs(FileUploaderCompletedResult.Failed) : new FileUploaderCompletedEventArgs(FileUploaderCompletedResult.Ok)); How I have to declare "FileUploaderCompletedResult"? – user2272143 Jun 12 '21 at 13:37