Uploading multiple files with C#

Have you ever been in a situation where you needed to upload multiple files to a remote host and pass additional parameters in the request? Unfortunately there’s nothing in the BCL that allows us to achieve this out of the box.

We have the UploadFile method but it is restricted to a single file and doesn’t allow us to pass any additional parameters. So let’s go ahead and write such method. The important part is that this method must comply with RFC 1867 so that the remote web server can successfully parse the information.

First we define a model representing a single file to be uploaded:

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

And here’s a sample UploadFiles method implementation:

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 here’s a sample usage:

using (var stream1 = File.Open("test.txt", FileMode.Open))
    using (var stream2 = File.Open("test.xml", FileMode.Open))
    using (var stream3 = File.Open("test.pdf", FileMode.Open))
    {
        var files = new[] 
        {
            new UploadFile
            {
                Name = "file",
                Filename = "test.txt",
                ContentType = "text/plain",
                Stream = stream1
            },
            new UploadFile
            {
                Name = "file",
                Filename = "test.xml",
                ContentType = "text/xml",
                Stream = stream2
            },
            new UploadFile
            {
                Name = "file",
                Filename = "test.pdf",
                ContentType = "application/pdf",
                Stream = stream3
            }
        };

        var values = new NameValueCollection
        {
            { "key1", "value1" },
            { "key2", "value2" },
            { "key3", "value3" },
        };

        byte[] result = UploadFiles("http://localhost:1234/upload", files, values);
    }

In this example we are uploading 3 values and 3 files to the remote host.

Next time I will show how to improve this code by adding an asynchronous version using the TPL library in .NET 4.0.

Leave a comment

Your email address will not be published. Required fields are marked *

15 thoughts on “Uploading multiple files with C#”