Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Building an Imgur Client for Windows Phone - Part 5 - Uploading A Picture

02.14.2013
| 2970 views |
  • submit to reddit

If you missed the previous four parts, you can easily find them here:

Probably the most interesting part about the Imgur client that we are building is its ability to upload images alongside with the ability to visualize the main gallery stock. There is a dedicated endpoint that was designed to perform this task - /image.

Before going into the upload mechanics, let's recap how the user can select an image on a Windows Phone device. To read photo content from the device, you need to use PhotoChoserTask. More than that, it will also allow the user to take a new picture, if necessary. 

What I did in my application was simply add a Upload button and use the following snippet to convert the returned picture to binary content:

PhotoChooserTask task = new PhotoChooserTask() { ShowCamera = true };
task.Completed += (s,d) =>
    {
        if (d.TaskResult == TaskResult.OK)
        {                 
            byte[] buffer = new byte[16*1024];

            using (MemoryStream stream = new MemoryStream())
            {
                int read = 0;

                while ((read = d.ChosenPhoto.Read(buffer, 0, buffer.Length)) > 0)
                {
                    stream.Write(buffer, 0, read);
                }

                App.ServiceClient.UploadImage(stream.ToArray(), "WP Imagine Test", "Test");
            }
        }
    };
task.Show();

This will pass the array created from the image stream to the UploadImage function, which is my implementation of the upload mechanism. Take a look at it:

public void UploadImage(byte[] content, string title, 
    string description, Action<bool> onCompletion = null)
{
    string BOUNDARY = Guid.NewGuid().ToString();
    string HEADER = string.Format("--{0}", BOUNDARY);
    string FOOTER = string.Format("--{0}--", BOUNDARY);

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.imgur.com/3/upload");
    request.Method = "POST";
    request.Headers["Authorization"] = "Client-ID " + _clientID;
    request.ContentType = "multipart/form-data, boundary=" + BOUNDARY;

    StringBuilder builder = new StringBuilder();
    string base64string = Convert.ToBase64String(content);

    builder.AppendLine(HEADER);
    builder.AppendLine("Content-Disposition: form-data; name=\"image\"");
    builder.AppendLine();
    builder.AppendLine(base64string);
    builder.AppendLine(FOOTER);

    byte[] bData = Encoding.UTF8.GetBytes(builder.ToString());

    request.BeginGetRequestStream((result) =>
        {
            using (Stream s = request.EndGetRequestStream(result))
            {
                s.Write(bData, 0, bData.Length);
            }

            request.BeginGetResponse((respResult) =>
                {
                    try
                    {
                        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(respResult);
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            Debug.WriteLine(reader.ReadToEnd());
                        }
                    }
                    catch (WebException ex)
                    {
                        using (StreamReader reader = new StreamReader(ex.Response.GetResponseStream()))
                        {
                            Debug.WriteLine(reader.ReadToEnd());
                        }
                    }
                }, null);
        }, null);

}

It might seem quite long, but as a matter of fact, it performs a fairly simple task - create a multipart/form-data POST request. Notice that unlike the common approach, the base64 representation of the image content is not escaped - when building a multipart POST request, escaping the string will cause the API call to fail, so make sure you are not doing that. Also, you will still need to pass your client ID, even though you might upload the image anonimously (without being associated with a specific user account).

The way this method currently works, it will only upload the image without any metadata associated with it, such as a title and a description for it. Let's fix this and add conditionals that would check whether the title and description should be attached to the request.

if (!string.IsNullOrWhiteSpace(title))
{
    builder.AppendLine(HEADER);
    builder.AppendLine("Content-Disposition: form-data; name=\"title\"");
    builder.AppendLine();
    builder.AppendLine(title);
}

if (!string.IsNullOrWhiteSpace(description))
{
    builder.AppendLine(HEADER);
    builder.AppendLine("Content-Disposition: form-data; name=\"description\"");
    builder.AppendLine();
    builder.AppendLine(description);
}

Once again, we are following the standard multipart/form-data requirements to add additional parameters. Once the image is uploaded, you will be able to see both the title and description on the image page:


If the upload was successful, we can expect to get the following information:

  1. Image ID
  2. The delete hash
  3. Full image URL

Those can be easily deserialized, so I can easily piggyback on the existing Image model by simply adding a DeleteHash property and associated it with the deletehash field in the returned JSON string.

public class ImgurImage
{
    public string ID { get; set; }
    [JsonProperty(PropertyName="deletehash")]
    public string DeleteHash { get; set; }
    public string Title { get; set; }
    public Int64 DateTime { get; set; }
    public string Type { get; set; }
    [JsonProperty(PropertyName = "animated")]
    public bool IsAnimated { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public Int64 Size { get; set; }
    public Int64 Views { get; set; }
    [JsonProperty(PropertyName = "account_url")]
    public string AccountUrl { get; set; }
    public string Link { get; set; }
    public string Bandwidth { get; set; }
    public int Ups { get; set; }
    public int Downs { get; set; }
    public int Score { get; set; }
    [JsonProperty(PropertyName = "is_album")]
    public bool IsAlbum { get; set; }
}

When the image is returned, we no longer deal with an array of images, therefore the ImgurImageData model might not be as suitable. Instead, I created an ImgurAtomicImageData class:

using Newtonsoft.Json;

namespace Imagine.ImgurAPI
{
    class ImgurAtomicImageData
    {
        [JsonProperty(PropertyName = "data")]
        public ImgurImage Image { get; set; }
        public bool Success { get; set; }
        public int Status { get; set; }
    }
}

When the response is received I can use the generic DeserializeObject for ImgurAtomicImageData:

HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(respResult);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
    string jsonContent = reader.ReadToEnd();
    var imageData = JsonConvert.DeserializeObject<ImgurAtomicImageData>(jsonContent);
    Debug.WriteLine(jsonContent);
}

As a result, you will be able to get both the HTTP code and the core image model containing all three data fields that I mentioned above.


Congratulations, you can now upload images to Imgur from your Windows Phone!