Update 3-7-2015: While this approach does work, the most recommended way to store image files in conjunction with Mobile Services is to use Blob Storage. You can read a walkthrough of how that works here.
Windows Azure Mobile Services have been blowing up in popularity since the release a few weeks ago. One of the questions I’ve received most frequently is if there is anyway to store images in Mobile Services. The answer to this is a little bit yes and a little bit no. Before proceeding, if you haven’t already taken a walk through of Mobile Services, I’d highly recommend reading through my earlier post on connecting iOS to Windows Azure Mobile Services. The code I’ll show later in this article will build off the sample built in that post. If you want to skip all of the explanation, you can download the code for this sample from the savingImages branch in GitHub. If you decide to go right to the code, I would HIGHLY recommend reading the Disclaimer first.
Update: I finally had the time to create a sample that connects Mobile Services to Blob Storage which is a much better approach for storing images with Mobile Services. The way this works is that you request a Shared Access Signature (SAS) URL from your Mobile Service, the service talks to blob storage to generate the SAS and then returns it to your app, and then your app can upload the image bytes to the SAS URL safely. I'm leaving this post up for posterity but going forward I would highly recommend you follow the approach explained here (the iOS client is linked from this article).
JSON and Binary Data
JSON, the data format that Mobile Services uses to send data across the wire, does not natively support binary data types so you can’t post JSON as a column in the JSON you send to the server. If you try, you’ll get back this from your post to the server:
"code":400,"error":"The value of property 'columnName' is of an unsupported type."
What is interesting is that if you manually connect to the SQL database that stores the data for you mobile service (using either SQL Server Management Studio or the Manage button in the Windows Azure portal) there isn’t anything to stop you from adding columns of unsupported types to the database. When I first wanted to look at storing an image in the database for Mobile Services, the first thing I tried was adding a Varbinary column to my table and pulling that down into my client. This actually works, kind of. When you query the table from your iOS app the Varbinary column comes back looking like this in the JSON:
So you’re literally getting an array back of the bytes. As you can imagine, this wouldn’t really be ideal for taking and then reconstructing into an image. The bigger problem, though, is that you can’t post image data back to the server in the same format (you’ll get the error mentioned above). However, in trying this, I struck on another idea which does work.
While the method I’m about to describe will work, I wouldn’t recommend it unless you know what you’re doing. You’re better off with hosting a service in Windows Azure Websites which will get you a Shared Access Signature (SAS) for secure uploading to Windows Azure Blob Storage and then saving the blob’s URL to your Mobile Services database. If you’d like to take a look at doing this, you can read this series about mobile geolocation apps which talks about getting a SAS URL from a PHP service (running in Windows Azure Websites) and then uploading the image. The debate about whether or not you should even store image data in a database is much to large to even be touched upon here but hopefully with this disclaimer in mind, you’ll know how to proceed.
The solution I’m going to explain today is to use a varchar (string) column to store the base 64 encoded string representation of an image in the Mobile Services database. Since Mobile Services is more than capable of handling string type data, it will treat this as such and now even realize it’s storing image data. We’re going to be basing our code off of the sample mentioned above. You can grab the code without any of the changes we’re about to do from the master branch of this GitHub repository.
Changing the UI
The first thing we need to do is alter our user interface to facilitate the images. Open up MainStoryboard.storyboard and take a look at the view for the TodoDetailsViewController (it should be at the far right). Before we make any changes the view will look like this:
We’re going to compact things a bit so we can keep our simple view and add the new controls. Everything in the top half view should be moved up and a button should be added to the left side and an ImageView to the right side. In the bottom half view, you can move things up and just add an ImageView beneath the “Mark Todo Complete” button. When you’re done, the UI should look like this:
Open the assistant editor and control + click and drag the new UI elements over to create an IBOutlet for each of the image views and an IBAction for the button. We’re also going to need to change TodoDetailsViewController.h so that the class implements the UIImagePickerControllerDelegate and UINavigationControllerDelegate. When you’re done, your header file will look something like this:
Now before we code the implementation, let’s solve a problem with encoding our string.
Handling Base 64 encoding and decoding
Simply put, there isn’t anything built into Objective-C or the iOS Framework to facilitate base 64 encoding and decoding. Thankfully, a quick search on the internet turns up this. You’ll need to download the NSData+Base64 class and header and drop them into your project. After doing so, make sure you open your project file and go to the Build Phases tab. You’ll need to expand Compile Sources and add NSData+Base64.m. When you’re done it will look like this:
Now that we can encode and decode in base 64 to our heart’s content, let’s move on to TodoDetailsViewController.m.
Implementing the Methods
First things first, add the base 64 class to your import statements:
Now let’s implement the tapSelectImage method:
Here we’re creating a UIImagePickerController, telling it to use the gallery, telling it to use the view controller as it’s delegate, and presenting it to the user. The imagePickerController delegate method will use the image returned to set the image property on our ImageView:
Now if you run your app and tap select image and pick one from the gallery, it should show up next to the “Select Image” button.
Let’s handle saving the image next.
Saving the image
Go to the tapSaveTodo method. After we’ve set headers on theRequest we will pull the image property from the ImageView and base 64 encode it (provided there is an image selected).
Then we add the image field into the NSDictionary we create and serialize to JSON. The column name I’m using here is “coltest” but since we’re using Mobile Service’s dynamic schema, you could name it anything you want. In this situation, if there is image data we’ll pass over the encoded string for it and if there isn’t, we’ll send over nil. Now, you could run your app and try out saving, but why don’t we make loading work as well so we can test it all at once.
Loading the image
Due to the fact that we’re storing the data that comes back from the server as an NSDictionary with all fields returned left inside it, and since we’re storing that NSDictionary on the app delegate, the change we need to make to load the image for an existing todo item is extremely small:
Here in the viewDidLoad method we are pulling the NSString value out for “coltest”. If that isn’t null, we then base 64 decode it into NSData. We then create an UIImage from the NSData and use that to set the image property of the ImageView. Let’s run it.
Running the app
When you run your app, you should be able to select an image and then save your todo item. When you tap back into that todo item, you should see the image along with the title:
Additionally, if we log into the Windows Azure portal and go to our mobile service’s data page and view our table data, we can see that the string representation of our data is showing up in “coltest”:
Today we walked through one way to store images with Windows Azure Mobile Services. As noted in the disclaimer, this technique shouldn’t be used without some consideration. If you do decide to use it, one way you could mitigate long load times (you may have noticed this sample running slower due to the data being returned when it selects all of the todos) is to only select the non-image columns when you are pulling data for the TableView and select the full todo item (including the image) when the user taps into a todo. This still doesn’t solve some of the problems like increased database bandwidth and size, but it is a way of solving the problem. You can access the completed source code for this demo under the savingImages branch of this GitHub repository (just remember that you’ll need to follow the above instructions for downloading the NSData+Base64 files and including them in your compile sources as well as configure your app in the Constants.m file).