READ THIS FIRST
Update: 3-4-2016: If you're finding this blog and looking for information related to Azure Mobile Services, I'd strongly recommend checking out Azure Mobile Apps. Azure Mobile Apps is a new version (consider it a v2) of Azure's mobile backend support. All of the same features of Azure Mobile Services are there, with a lot of other very cool features to go along. You can read more about Azure Mobile Apps, and how to transition from Azure Mobile Services, here.
Update 8-29-2013:
Since the release of this article, the iOS SDK for Mobile Services has changed so that you can no longer send a zero over as the ID. This will lead to an invalid ID error. I've adjusted the code shown here to send over the value 1 for the ID. Since the ID is ignored on the server side for our storage project, this shouldn't affect anything.
This article is the companion to one I just posted about connecting Mobile Services to Windows Azure Table and Blob Storage. Prior to taking a look through this article and the mentioned code, I would go through the original article on Mobile Services and Windows Azure Storage. This article will cover how to connect the Mobile Service we set up there with an iOS client using the Mobile Services SDK for iOS. All of the source code for this iOS app is available here in GitHub. Since this app deals with talking to Table and Blob Storage through Mobile Services, I’m only going to highlight here when we’re talking to Mobile Services from iOS and maybe a few other important pieces. If you want to see how the entire app works (such as how we display table names and blob names in a table view) take a closer look at the source code.
Before you run the app
The only thing you need to do before you run this app is adjust the init method of the StorageService class to use your Mobile Service’s URL and Application Key. You can grab these by logging into the Windows Azure portal, going to your Mobile Service, and clicking the MANAGE KEYS button when viewing your service’s dashboard. Those values go into the MSClient’s clientWithApplicationURLString method call.
The Storage Service
All of the “action” for talking to our Mobile Service is done inside of the StorageService class. For that reason, the majority of the code we’ll look at today is from that class. The first thing we’ll look at is the init method for the class:
As mentioned above, you’ll need to set the mobileserviceurl and applicationkey to your Mobile Service’s before running. After that we’re just setting up some local properties like the tables we’ll use to get access to each of the Mobile Service’s tables we set up in the first article. That’s all there is as far as setup goes. Now let’s look at each individual operation in the class.
Reading tables
Reading tables is done in the same way we would if we were going to read data normally from Mobile Services:
Since the iOS SDK relies upon NSDictionary which isn’t typed, we don’t need to do anything special to get the data on tables out and into a local array. After we pull down that table, we can go through the array and use each item’s TableName property to fill our table view.
Creating tables
Creating tables is just like a normal operation where we’re just going to pass the table name over in the NSDictionary we’re going to insert:
Deleting tables
For deleting tables, we have to do things a little differently than normal Mobile Services’ deletes:
Here, we’re passing the table name over as parameters. This is necessary because when you call delete, everything is stripped out of the JSON item that is sent over but the id. The idea is that on the server side you just delete by ID so you don’t need to send the other information over the wire. This means we HAVE to pass the table name over through some other means, hence, parameters.
Reading table rows
Once we’ve tapped on a table in our table view, we can then call the TableRows read method to get all of the row data back:
Here we’re constructing a custom query string to pass into the readWithQueryString method. We’re doing this because on the server side we’re looking at the request.parameters object to get the table name we’re going to read. If you look at how we’re handling displaying the data for each row in the table view, you’ll see some “somewhat” complicated code that I pulled out of the old Windows Azure Toolkit. Since just displaying the data in a table view like that is pretty unlikely to happen, I’m not too worried about how the data is exactly displayed as long as it works.
Inserting table rows
Inserting rows requires us to send over both the NSDictionary with all of the row properties as well as a set of parameters with the table name in them:
Deleting table rows
Just like for deleting tables, we need to handle things a little differently to delete table rows:
Here our parameters have three items in them: the table name, the Partition Key, and the Row Key.
Updating table rows
Just like with inserting, we pass over the NSDictionary with all of the properties on it and the parameters array with the table name in it:
Table Storage itself handles updating the item by the table name, Partition Key, and Row Key. Now that we’ve looked at all of the Table operations, let’s move over to Blobs.
Reading containers
Reading the containers is a normal read against that table:
Creating containers
Creating retainers requires us to pass in two things, the NSDictionary item with the container name, and a NSDicationary of parameters:
The parameters contain the flag for whether or not the container should be public or private. If the container is created as public, we’ll be able to load up the blobs without needing a Shared Access Signature (SAS, but more on that later).
Deleting containers
Deleting containers, just like deleting tables, requires that we send an id over. We can’t send the container name as part of the item with the id because everything but the id will get stripped off:
Instead the container name is sent over as part of the parameters.
Reading blobs
Reading the blobs just requires us to send over the name of the container we’re looking in. Just like when we’re getting table rows, we do this with a custom query string:
Inserting blobs and loading the data
Creating new blobs is handled a little bit interestingly. In order to create a blob, our Mobile Service tells Blob Storage it wants to create a new blob with a specified container name and blob name. It does this by generating a Shared Access Signature which we can use as part of a URL to then post the blob data on the client. So, from the client we call the getSasUrlForNewBlob method:
This passes the container and blob name over as parameters and it comes back with the SAS URL from the server. Once we get that back, we can then post those bytes from the client which it does with a NSMutableURLRequest and NSURLConnection. Now in the case of private containers where we can’t just use the URL of the blob to access the data,(in our case we’re saving images, so we can’t just use the blob URL to set the source of the image) we need to create a SAS URL to access that data. This method pulls double duty by fetching a read and write URL from the server. In reality we might want to split this functionality between two methods for security purposes but for this demo it’s been combined. You can see how we’re using this to load the data by checking out the tappedLoadWithSAS method in BlobDetailsViewController.m.
Deleting blobs
Deleting blobs is similar to deleting tables or containers:
We pass over an empty id and then put the container and blob name in the parameters that get sent over.
Finished with storage
That’s all we need to do from the client side. Many of these method calls you’ve seen don’t really do anything differently than calls to Mobile Services when we’re using SQL Database storage normally. Thankfully we can abstract our client away enough to not worry about some of these things. In the case of methods where we need to pass additional information, like deletes where there isn’t a JSON item being sent over, we use the option parameters value to send the information over. One important point to make is that for the purposes of this demo, everything should work regardless of what information is stored in the tables (and to some degree the blobs). In reality if you were building an application that was going to make use of Blob and Table Storage from Mobile Services, you’d in theory know what data was in your tables and blobs so things wouldn’t be handled as generically as I’ve done in this client application.
Using NSNotificationCenter
For this application I wanted to explore a new way of reloading the data on my table views. Normally I would generate delegates and trigger a data refresh on the delegate, which would reload the table view, whenever the data changed. For this sample I used NSNotificationCenter instead. The table view classes are setting themselves as observers of whatever the reload method is (for example refreshTableRows, refreshBlobs, etc and whenever the data changed, a notification was sent to say that data had changed. This was a pretty easy way to handle sending information back that didn’t require very much work (and arguably less code than implementing the delegates that would be used instead).
Conclusion
Today we looked at how to implement a client that connects to a Mobile Service that uses Table and Blob Storage. Some aspects of it are no different from using normal SQL Database storage. Some of it requires a few additional steps. With the knowledge from this article and the server side one, you should now know how to comfortably combine SQL Database, Table, and Blob Storage. A good knowledge of Blob Storage and getting SAS URLs (and how to upload blobs with them) is important even if you stick to SQL Storage since it, and the JSON that is used between the client and Mobile Services, isn’t really meant for file storage. Good luck and remember that the source code for this and the server side scripts is all available here on GitHub.
39 Comments
Paulo Cauca
Hello, Chris, can you help me about dificult bellow ?
How can I pass parameters to read function in Windows Azure mobile service ?
In code bellow I call the read function , but I want pass Parm1 to manipulate in Windos Azure.
[itemTable readWhere:predicate completion:^(NSArray *items, NSInteger totalCount, NSError *error) {
I want in Windows Azure make something like this...
function read(query, user, request) {
var parm1 = query.parameters.Parm1;
request.execute();
}
Tks for help me.
Paulo
Chris
Hi Paulo,
To pass parameters, you'll need to use MSTable's readWithQueryString method. So something like this:
NSString *queryString = [NSString stringWithFormat:@"param1=%@", @"MyParam"];
[self.tableRowsTable readWithQueryString:queryString completion:^...
Then in your script you access it using the request object like this:
request.parameters.param1
Chris
Mosib
Hi Chris,
I have UISearchBar in my app and i want to search for data against string from particular column in windows azure mobile service.. How can i put the condition for checking if any row's text matches with my sent string. like for example here is my query :
select * from table where column1 like "%" + user_search +"%"
and column2 = myAnotherCondition
Quick reply will be highly appreciated.
thanks!
Chris
Sorry for the slow reply Mosib. If you're just doing queries against the built in SQL Storage (i.e. Mobile Services Tables) you'll use NSPredicates and the MSQuery object. Take a look at the quick start todo application to see how a simple query is done and then you can look at the MSQuery reference here: http://www.windowsazure.com...
hector enrique martinez reyes
Hello Chris
in Blob storage the url is mobile serivce or account name to release a connection ?
for example:
MSClient *newClient = [MSClient clientWithApplicationURLString:@"https://mobileserviceurl.az..." withApplicationKey:@"applicationkey"];
or
MSClient *newClient = [MSClient clientWithApplicationURLString:@"https://accountname.blob.co..." withApplicationKey:@"primaryapplicationkey"];
and how to use the containersTable and blobsTable in [getTable:@""]; because if I connect with mobile service I get the following error,
2013-08-02 19:00:12.916 StorageDemo[2356:c07] ERROR Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Invalid value around character 3.) UserInfo=0x888c630 {NSDebugDescription=Invalid value around character 3., com.Microsoft.WindowsAzureMobileServices.ErrorResponseKey=<nshttpurlresponse: 0x888e590="">, com.Microsoft.WindowsAzureMobileServices.ErrorRequestKey=<nsmutableurlrequest http:="" accountname.blob.core.windo...="" tables="" blobcontainertest="">}
I looked for references on errors and how to connect to blob storage but do not mention anything about this
I hope you can help me and your help is appreciated
yingja!
Hi Chris, thanks for this awesome post!
I have been using getSasUrlForNewBlob to post picture blobs to my azure containers, however, sometimes I was returned NULL, I am wondering besides following your instructions (client&server), what else am I maybe missing?
Chris
Are you seeing any logged information from your Mobile Service script when you get the NULL response?
yingja!
Yes, here's the log:
An unhandled exception occurred. Error: One of your scripts caused the service to become unresponsive and the service was restarted. This is commonly caused by a script executing an infinite loop or a long, blocking operation. The service was restarted after the script continuously executed for longer than 1000 milliseconds.
at EventEmitter.<anonymous> (C:\DWASFiles\Sites\FilmDiary\VirtualDirectory0\site\wwwroot\runtime\server.js:84:17)
at EventEmitter.emit (events.js:88:20)
Chris
Well it sounds like one of your scripts is messed up. I would suggest a poor man's debugging and putting in console statements in whatever script is being called so you can see which line in the script is causing the problem.
yingja!
I was using exactly the same script you provided in another post (http://chrisrisner.com/Mobi..., I don't think the script is the issue, and the NULL situation does not happen every time (around 30%, which is a lot higher than I would expect). Maybe it's because I set the mobile service to run in asia, and testing from U.S., and that could cause some network timeout?
Chris
One of your scripts is causing the error you posted above. You can figure out exactly which line by putting in more console.log statements into the script. This should help us narrow down specifically which part of the script is causing it to happen. If your Mobile Service and your storage account are both in Asia, network issues shouldn't be causing the script to run long as the error is indicating.
Chris
It looks like there is a production problem currently when loading some scripts that cause the above mentioned error. A fix will be out soon so it should go away.
yingja!
Thanks a lot Chris! Your help is much appreciated!! :)
yingja!
Hi Chris, this sasUrl creation problem seems solved, appreciate that. One thing very weird is that, I found from my telemetry many Chinese users cannot post images to sasUrl (blobs are not created), while U.S. users always succeed. Do you know what might be going wrong?
Chris
Any insight into what's going wrong on the client side? Are they getting a SAS and then unable to reach the storage endpoint due to a timeout or 404 or something else?
alessandr0
Hi, is there a way to check if a blob name exists without to read all them? This is to avoid to override it with the insert method.
Thanks
Chris
You can call getBlobProperties (using the Azure module) and pass in the container and blob name and see if you get anything back.
Jay Mayu
Christ thanks for the tutorial. I'm planning to upload an image from JS/HTML5 sdk. But still I don't understand how to pass image as a parameter and insert it . Some resources I can dig up a bit plz?
Chris
Are you building a purely HTML5/JS site that you want to upload images with? If so, you'd need to get the bytes fro the image and then post them to the SAS URL just like we're doing above. It looks like this is one way to accomplish that: http://stackoverflow.com/qu...
Hernando
Hi Great article , but is now out date since the Windows Azure SDK for iOS has been updated.
Replace all "getTable"
self.tablesTable = [_client getTable:@"Tables"]; with
self.table = [_client tableWithName :@"Table"]:
And also the Storyboard Layout needs updated.
Christ in this article from windows azure resource "
Upload images to Windows Azure Storage by using Mobile Services" ,
http://www.windowsazure.com...
they only inserted one script in the main table , can we do this for iOS as well ?
Thanks for your help.
Hernando
Hi can you update the code , the Windows SDK has been update and you need to replace "getTable" with "tableWithName".
Chris
Hi Hernado, this sample is using the iOS SDK though getTable will still work (it's been deprecated but still works fine). If i have a chance, I'll update the code in GitHub but it should be pretty trivial for you or anyone that pulls it down to do it for now as well.
Alex Cannon
Related to this, the example code does not work for me with the latest iOS SDK (v 1.2.3). I swapped out the older framework in your example project with v1.2.3 and only renamed all `getTable` and `ApplicationWithURL:withApplicationKey` calls. Once the app builds, clicking "Blob Storage" crashes the app with the following output:
https://gist.github.com/aca...
I'm having similar problems using the StorageService class in my own app. It works fine with the older SDK but crashes with v1.2.3.
Alex Cannon
Related to this, I can't get the example project to work with the latest version of the SDK (v1.2.3). I swap out the older SDK its currently using for v1.2.3 and only rename a few function calls as need (e.g. getTable to tableWithName, ApplicationURLWithString:WithApplicationKey: to ApplicationURLWithString:applicationKey:, etc.). After the app builds, clicking "Blob Storage" crashes with the following output:
https://gist.github.com/aca...
My personal app has similar issues. It works fine with the same older version of the SDK that's used in the example project, but one that's swapped out for v1.2.3 it crashes as well:
https://gist.github.com/aca...
Chris
Just tested it out, it looks like the MSFilter method signature changed. It should now be this:
- (void) handleRequest:(NSURLRequest *)request next:(MSFilterNextBlock)onNext response:(MSFilterResponseBlock)onResponse {
Replace your existing filter method sig with that and things should work for you again. You can use the same code body from the existing filter method.
Rana Nile
thanks for sharing
شركة تخزين عفش بالرياض
شركة ترميمات بالرياض
شركة دهانات بالرياض
شركات تنظيف المنازل بجدة
شركة كشف تسربات المياة بجدة
شركة مكافحة حشرات في جدة
تنظيف خزانات بجدة
نقل عفش جدة
شركات تنظيف منازل بجدة
عزل بالرياض
عزل اسطح بالرياض
عزل خزانات بالرياض
عملية تصغير المعدة
عملية تكميم المعدة
عمليات تدبيس المعدة
شركات مكافحة القوارض بالرياض
مكافحة حشرات بالرياض
رش مبيدات بالرياض
شركة عزل خزانات بالرياض
شركة تخزين عفش بالرياض
شركة تنظيف بيارات بالرياض
تنظيف مجالس بالمدينة المنورة
تنظيف منازل بالمدينة المنورة
تنظيف فلل بالمدينة المنورة
تنظيف مسابح بجدة
تنظيف موكيت بجدة
تنظيف شقق بجدة
شركة مكافحة حشرات بالدمام
شركات تنظيف المنازل بالدمام
شركة تسليك مجاري بالدمام
تنظيف بالرياض
كشف تسربات المياه بالرياض
تنظيف فلل بالرياض
اثاث مستعمل للبيع بالرياض
شركة كشف تسربات المياة بجدة
بيع اثاث مستعمل بالرياض
شركة رش مبيدات بالأحساء
افضل شركات بالسعودية
افضل شركات بالسعودية
افضل شركات بالسعودية
مكافحة حشرات بالمدينة المنورة
شركة تنظيف شقق بالمدينة المنورة
شركة تسليك مجاري بشقراء
شركة كشف تسربات المياه بضرماء
شركة تنظيف بيارات بالدلم
شركة تنظيف بضرما
George Lovitz
Hi Chris,
First, awesome tutorial and info. Helped me greatly but I am running into a problem. I can run your same project fine and have been able to create containers and input blobs no problem. Then, I attempted to add it to my project (which I am running the latest Azure framework) and I always get a warning in the implementation stating that the following method/protocal is not declared...although it is in the .h and in the actual .m:
(void) handleRequest:(NSURLRequest *)request
onNext:(MSFilterNextBlock)onNext
onResponse:(MSFilterResponseBlock)onResponse
Always warns not declared but am able to compile and run, but when it attempts to create the container, errors with same as above. Any thoughts on this? I did think about just deleting the latest azure framework and using the one you complied with but thought I would try you first. Once again, thanks. You have been a great source of help throughout my xcode experience!
Geo...
Chris
Sorry for missing this George. If you, or anyone else is looking, this is because the latest version of the SDK for Mobile Services for iOS has changed the handelRequest method. I would suggest pulling down the iOS Quick Start for Mobile Services and comparing how that implements the method and compare the two.
Guest
Thanks for the info. Is there an available version of this CloudStorage library that is compatible with ARC mode?
Chris
There isn't a "Storage" library per say, but the Mobile Services SDK that is used above is compatible with ARC.
young
Thanks for the good example! Just some notes on running the demo app on the current Xcode for beginners like me:
1. Set the simulator to iPhone4S or iPhone5 (the code doesn't build with 5S or later)
2. Add self.navigationController.navigationBar.transparent = NO; in viewDidLoad() to prevent navigationBar blocks input forms
Chris
Thanks for providing this info for other readers. I haven't had a chance to go back and update for newer versions of iOS
Cepstrum
Hi. I'm trying to upload to the blob storage but I can't figure out what the "BlobBlobs" table should be. Is it just a random table or should it somehow be associated with the SAS? If I use a random table, the completion block returns a dictionary with key "id" and value with id of the created row and not a SAS URL.
Cepstrum
Sorry, I didn't see the other post about adding the BlobBlobs table and script. It all makes sense now! Thanks for the only iOS example in the public world!
Chris
Happy you found it! Sorry I didn't have a chance to reply sooner!
Eduardo Rosas Osorno
How does one upload an image to the blob one having the sasUrl?
Chris
Once you have the SAS URL you can just do a POST request against that URL with the image as the content. Take a look at the postBlobWithUrl method here: https://github.com/Azure-Sa...
Saerom HA
Hi Chris. Thank you for this post, it's so helpful.
I'd like to check out the full source code as well you linked in this article, but I've got 404 error when I click it.
Could you figure out what's wrong with it?
Thanks.
Saerom HA
I've found it by myself. The link has been changed I guess.
It's https://github.com/arnavgau....