Upload to Azure Storage REST API from Xamarin

Microsoft

To the cloud with our data -- in a hot air balloonIt surprised me to find that uploading an image to Azure Storage using the REST API with Xamarin iOS wasn't straightforward. After a lot of research and by combining a couple techniques, we can easily upload a UIImage to blob storage using the Azure Storage REST API. Here's what you'll need:

  1. Azure Mobile Services (and client library)
  2. HttpClient

I'm not going over how to create a storage location and container nor an Azure Mobile Service. Those steps are basic and covered elsewhere on the internet.

By using the Azure Mobile Services, we don't have to worry about storing our Azure Storage secret key on a mobile device. This technique acquires a Shared Access Signature (SAS) from the mobile service and uses that for authorization on the device.

If you're writing an app that already uses Azure Mobile Services, adding this API method is no big deal.

Create a Mobile Service API Endpoint

This part is a derivation of the technique described in the Mobile Services documentation. Unfortunately, that example uses an Azure Storage client library which isn't available for Xamarin. The only difference between the example and my code is that the example used a standard table item, but I created a custom API inside the mobile service and called it imagesas. Here's the code:

var azure = require('azure');
var qs = require('querystring');
var appSettings = require('mobileservice-config').appSettings;

exports.get = function(request, response) {

    // Get storage account settings from app settings.
    var accountName = appSettings.STORAGE_ACCOUNT_NAME;
    var accountKey = appSettings.STORAGE_ACCOUNT_ACCESS_KEY;
    var host = accountName + '.blob.core.windows.net';
    var containerName = "images";


    // If it does not already exist, create the container
    // with public read access for blobs.
    var blobService = azure.createBlobService(accountName, accountKey, host);
    blobService.createContainerIfNotExists(containerName, {
        publicAccessLevel: 'blob'
    }, function(error) {
        if (!error) {

            // Provide write access to the container for the next 5 mins.
            var sharedAccessPolicy = {
                AccessPolicy: {
                    Permissions: azure.Constants.BlobConstants.SharedAccessPermissions.WRITE,
                    Expiry: new Date(new Date().getTime() + 5 * 60 * 1000)
                }
            };

            // Generate the upload URL with SAS for the new image.
            var sasQueryUrl =
                blobService.generateSharedAccessSignature(containerName,
                                                          request.query.resourceName, sharedAccessPolicy);

            var item = {};
            // Set the query string.
            item.sasQueryString = qs.stringify(sasQueryUrl.queryString);

            // Set the full path on the new new item,
            // which is used for data binding on the client.
            item.imageUri = sasQueryUrl.baseUrl + sasQueryUrl.path;

            response.send(statusCodes.OK, item);

        } else {
            console.error(error);
        }

    });
};

This returns an item with two properties like this:

{
    sasQueryString: 'the computed query string',
    imageUri: 'https://imageUri'
}

The endpoint expects a parameter resourceName which is the name of the you intend to upload. Note that in my case, I already know the name of the container, but you can pass that in if you don't already know it at compile time.

Let's switch over to the iOS C# Code.

Retreive the SAS

Now that we've got our mobile services code done, we need to utilize that API endpoint from our C# code. This is typical Azure Mobile Services Xamarin iOS code, so I won't go into that. You can read more about how to do that in the documentation.

The Response Object

using System;
using Newtonsoft.Json;

public class ImageSASResponse
{
    [JsonProperty(PropertyName = "sasQueryString")]
    public string SASQueryString { get; set; }

    [JsonProperty(PropertyName = "imageUri")]
    public string ImageUri { get; set; }
}

The Azure Mobile Service Call

public async Task GetImageUri(string imageId) {

    Dictionary apiParameters = new Dictionary ();
    apiParameters.Add ("resourceName", imageId + ".jpg");

    var imageSas = await client.InvokeApiAsync ("imagesas", HttpMethod.Get, apiParameters);
    return imageSas;
}

This code snippet assumes that you've already created the Mobile Services client somewhere else. Also, in my example, I'm only sending JPEGs, so I'm tacking on a file extension to the resourceName sent to the service. Your code will probably be different.

Upload to Azure Blob Storage

I used the ModernHttpClient by Paul Betts, because it's fast and I wanted a hook for getting upload progress messages. If you use plain 'ol HttpClient, your code will look just a little different.

This is a derivation of a blog post from Nick Harris where he uses HttpClient to connect to the Azure Storage REST API on Windows 8.

HttpClient _httpClient;
NativeMessageHandler _progressMessageHandler;

_progressMessageHandler = new NativeMessageHandler ();
_httpClient = new System.Net.Http.HttpClient (_progressMessageHandler);

var imageUri = await GetImageUri (Guid.NewGuid ().ToString ());
var jpgData = image.AsJPEG();
var content = new ProgressStreamContent (jpgData.AsStream ());

content.Headers.Add ("Content-Type", "image/jpeg");
content.Headers.Add("x-ms-blob-type", "BlockBlob"); 

var response = await _httpClient.PutAsync (new Uri (imageUri.ImageUri + '?' + imageUri.SASQueryString), content);

After this code runs, head on over to your favorite Azure Storage explorer and you'll see that the image you wanted to upload is now there. Or, just fire up a browser: the image location is in imageUri.ImageUri.

Summary

This post explains how, if you're building an iOS app using Xamarin, to upload an UIImage to Azure Storage using the Azure Storage REST API and HttpClient.

Aside from the UIImage manipulation and the use of the ModernHttpClient, much of this is reusable on other C# based platforms. On most of those platforms, the provided Azure Storage client library works, but in those cases where there is no client library available, this technique works.

Music for this post: Weird Al Yankovic: Mandatory Fun