Http multipart/form-data Upload in Windows Store Apps – Boredom Challenge Day 16

Standard

If a service you want to use does not have an SDK or library for the programming language you are using, chances are you will be using their REST API to interact with the system. REST API works – excluding the technical specifications – by putting our parameters in the http query string and/or the http header and using Http POST, GET, PUT and DELETE methods to tell the API what we wish to do. For example;

This is the code used if we wish to post something in Facebook for .NET SDK:

var postparameters = new
{
    access_token = fbAccessToken,
    message = TextBoxPost.Text
};
dynamic result = await facebookClient.PostTaskAsync("me/feed", postparameters);

Doing the same action RESTfully would be like this:

                       HttpRequestMessage request = new HttpRequestMessage(
                                System.Net.Http.HttpMethod.Post,
                                new Uri(String.Format("https://graph.facebook.com/me/feed?access_token={0}&message={1}", fbAccessToken, WebUtility.HtmlEncode(TextBoxPost.Text))));

                        var response = await client.SendAsync(request);

As you can see, we set our parameters by adding them to the end of our URL. Actually, the former solution wraps the latter one for us, meaning that Facebook for .NET SDK gets the parameters from us and prepares the http requests itself to reduce our work amount.

Anyway, as I’ve said, we usually need to use REST APIs when we don’t have an SDK or library for the service we want to use. Judging from the code example above, it looks easy, right? Well, yes it is. As long as you have the documentation for it, you wouldn’t have any problems. Unless… you wanted to upload a file. Think of it, I said we put the parameters to the URL, but how do we do this with a file?

This is where multipart/form-data comes in, which is a standard way of encoding the files as byte arrays and sending them over with the http request. However, preparing our file as multipart/form-data is far from trivial, because it is very specific and requires you to delve deep into internet standards definitions to understand (if you wonder, try reading it: RFC 2388). And finally, if you wish to use it in Windows Store apps, another difficulty is that since the .NET classes are different for WinRT, the most common solutions on the internet are not usable in Windows Store apps. As of writing this, I was unable to find a working code sample.

Until now, of course. 🙂 In this article, we’ll see how we can prepare an http multipart/form-data request in Windows Store apps and upload a picture to Facebook with this method.

For this purpose, I’ll use the example Facebook app I’ve created in my previous blog post (you can get it here) and change it’s photo uploading code, with the following:

using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
        StorageFile pictureToUpload;

In ButtonPost_Click (only changing the picture uploading code):

                    else if (pictureToUpload != null)
                    {
                        byte[] fileBytes = null;
                        using (IRandomAccessStreamWithContentType stream = await pictureToUpload.OpenReadAsync())
                        {
                            fileBytes = new byte[stream.Size];

                            using (DataReader reader = new DataReader(stream))
                            {
                                await reader.LoadAsync((uint)stream.Size);
                                reader.ReadBytes(fileBytes);
                            }
                        }
                        HttpRequestMessage request = new HttpRequestMessage(
                                System.Net.Http.HttpMethod.Post,
                                new Uri(String.Format("https://graph.facebook.com/me/photos?access_token={0}&message={1}", fbAccessToken, WebUtility.HtmlEncode(TextBoxPost.Text))));

                        string boundary = "---------------------------7dd36f1721dc0";
                        request.Content = new ByteArrayContent(
                            BuildByteArray("source", pictureToUpload.Name, fileBytes, boundary));
                        request.Content.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
                        request.Content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue(
                            "boundary",
                            boundary));

                        HttpClient client = new HttpClient();
                        var attachmentResponse = await client.SendAsync(request);
                        ProgressBar.IsIndeterminate = false;
                        ProgressBar.Visibility = Visibility.Collapsed;
                        ButtonPost.IsEnabled = true;
                        if (attachmentResponse.IsSuccessStatusCode)
                        {
                            MessageDialog messageDialog = new MessageDialog("Image successfully posted.");
                            await messageDialog.ShowAsync();
                        }
                        else
                        {
                            MessageDialog messageDialog = new MessageDialog("Could not post image.");
                            await messageDialog.ShowAsync();
                        }
                    }
        private byte[] BuildByteArray(string name, string fileName, byte[] fileBytes, string boundary)
        {
            // Create multipart/form-data headers.
            byte[] firstBytes = Encoding.UTF8.GetBytes(String.Format(
                "--{0}\r\n" +
                "Content-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\n" +
                "\r\n",
                boundary,
                name,
                fileName));

            byte[] lastBytes = Encoding.UTF8.GetBytes(String.Format(
                "\r\n" +
                "--{0}--\r\n",
                boundary));

            int contentLength = firstBytes.Length + fileBytes.Length + lastBytes.Length;
            byte[] contentBytes = new byte[contentLength];

            // Join the 3 arrays into 1.
            Array.Copy(
                firstBytes,
                0,
                contentBytes,
                0,
                firstBytes.Length);
            Array.Copy(
                fileBytes,
                0,
                contentBytes,
                firstBytes.Length,
                fileBytes.Length);
            Array.Copy(
                lastBytes,
                0,
                contentBytes,
                firstBytes.Length + fileBytes.Length,
                lastBytes.Length);

            return contentBytes;
        }

I’ve come up with this code by mixing several different solutions, so basically I don’t know their exact inner workings. Mainly, we prepare the http request with the necessary headers, and set its content with the correctly encoded and arranged byte array of our file. The more important part is, it works. 🙂 So let’s run it and see it in action:

1

2

That’s it. 🙂

Here‘s the source code of our example.

Thank you for reading.

Comment