920 likes | 1.16k Views
Protips for Windows Azure Mobile Services. Chris Risner Technical Evangelist 3-543. Introduction. Windows Azure Technical Evangelist. @ chrisrisner. Mobile Developer. http:// chrisrisner.com. Former .NET developer. Live in Washington. Grew up in Michigan.
E N D
Protips for Windows Azure Mobile Services Chris Risner Technical Evangelist 3-543
Introduction Windows Azure Technical Evangelist @chrisrisner Mobile Developer http://chrisrisner.com Former .NET developer Live in Washington Grew up in Michigan Co-Organizer of Seattle GDG
Agenda Mobile Services Recap Tips Tricks Tips Tricks Questions
Windows Azure Mobile Services Auth Data Notifications Logging & Diag Server Logic Scheduler Scale
You don’t need a different Mobile Service for each platform!
Single Platform Push Notifications Windows Store push.wns.sendToastText04(item.channel, {text1: text}, … ); Windows Phone push.mpns.sendFlipTile(item.channel, {title: text}, …); iOS push.apns.send(item.token, { alert: text, payload: { inAppMessage: Details }}, …); Android push.gcm.send(item.registrationId, item.text, …);
Multi-Platform Push Notifications function sendNotifications() { vardeviceInfoTable = tables.getTable('DeviceInfo'); deviceInfoTable.where({ userId : user.userId }).read({ success: function(deviceInfos){ deviceInfos.forEach(function(deviceInfo){ if (deviceInfo.uuid != request.parameters.uuid) { if (deviceInfo.pushToken != null && deviceInfo.pushToken != 'SimulatorToken') { if (deviceInfo.platform == 'iOS') { push.apns.send(deviceInfo.pushToken, { alert: "New something created" } , { //success / error block}); } else if (deviceInfo.platform == 'Android') { push.gcm.send(deviceInfo.pushToken, "New something created", { success / error block}); } } } }); } }); }
Don’t forget to check the response on error (or getFeedback for APNS)Also, check out Delivering Push Notifications to Millions of Devices – Friday @12pm
Create a tableUse it’s endpointDon’t call request.Execute
Custom API • Non-table based scripts • Accessible from • GET • POST • PUT • PATCH • DELETE • Permissions based
It’s doableIt’s not perfect Scripts and the Azure module
Reading Tables varazure = require('azure'); function read(query, user, request) { varaccountName = 'accountname'; varaccountKey = 'Accountkey------------nKHDsW2/0Jzg=='; var host = accountName + '.table.core.windows.net'; vartableService = azure.createTableService(accountName, accountKey, host); tableService.queryTables(function (error, tables) { if (error) { request.respond(500, error); } else { request.respond(200, tables); } }); }
Reading Table Rows var azure = require('azure'); function read(query, user, request) { varaccountName = 'accountname'; varaccountKey = 'Accountkey------------nKHDsW2/0Jzg=='; var host = accountName + '.table.core.windows.net'; vartableService = azure.createTableService(accountName, accountKey, host); vartq = azure.TableQuery .select() .from(request.parameters.table); tableService.queryEntities(tq, function (error, rows) { if (error) { request.respond(500, error); } else { request.respond(200, rows) } }); }
Creating Containers var azure = require('azure'); function insert(item, user, request) { varaccountName = 'accountname'; varaccountKey = 'Accountkey------------nKHDsW2/0Jzg=='; var host = accountName + '.blob.core.windows.net'; varblobService = azure.createBlobService(accountName, accountKey, host); if (request.parameters.isPublic == 1) { blobService.createContainerIfNotExists(item.containerName ,{publicAccessLevel : 'blob'} , function (error) { if (!error) { request.respond(200, item); } else { /* error */ request.respond(500);} }); } else { blobService.createContainerIfNotExists(item.containerName, function (error) { if (!error) { request.respond(200, item); } else { /*error */request.respond(500); } }); } }
Reading and “Creating” Blobs var azure = require('azure'), qs= require('querystring'); function insert(item, user, request) { varaccountName = 'accountname'; varaccountKey = 'Accountkey------------nKHDsW2/0Jzg=='; var host = accountName + '.blob.core.windows.net'; varblobService = azure.createBlobService(accountName, accountKey, host); varsharedAccessPolicy = { AccessPolicy: { Permissions: 'rw', //Read and Write permissions Expiry: minutesFromNow(5) } }; varsasUrl = blobService.generateSharedAccessSignature(request.parameters.containerName, request.parameters.blobName, sharedAccessPolicy); varsasQueryString = { 'sasUrl' : sasUrl.baseUrl + sasUrl.path + '?' + qs.stringify(sasUrl.queryString) }; request.respond(200, sasQueryString); } function minutesFromNow(minutes) { var date = new Date() date.setMinutes(date.getMinutes() + minutes); return date; }
The REST API Base REST API Endpoint URL http://Mobileservice.azure-mobile.net/tables/* Data Operations and their REST Equivalents
Sending an Email //var crypto = require('crypto'); //item.tempId = new Buffer(crypto.randomBytes(16)).toString('hex'); function sendEmail(item) { varsendgrid = new SendGrid('myaccount@azure.com', 'mypassword'); var email = { to : item.email, from : 'from-me@chrisrisner.com', subject : 'Welcome to MyApp', text: 'Thanks for installing My App! Click this link to verify:\n\n' + 'http://myapp.azurewebsites.net/activate.html?id=' + item.id + '&tid=' + item.tempId, createDate : new Date() }; sendgrid.send({ to: item.email, from: email.from, subject: email.subject, text: email.text }, function(success, message) { // If the email failed to send, log it as an error so we can investigate if (!success) { console.error(message); } else { saveSentEmail(email); } }); }
SOME It’s awe
Sending Version Info with Each Request - (void)handleRequest:(NSURLRequest *)request next:(MSFilterNextBlock)next response:(MSFilterResponseBlock)response { MSFilterResponseBlockwrappedResponse = ^(NSHTTPURLResponse *innerResponse, NSData *data, NSError *error) { response(innerResponse, data, error); }; // add additional versioning information to the querystring for versioning purposes NSString *append = [NSStringstringWithFormat:@"build=%@&version=%@", self.build, self.version]; NSURL *url = nil; NSRange range = [request.URL.absoluteStringrangeOfString:@"?"]; if (range.length > 0) { url = [NSURL URLWithString:[NSStringstringWithFormat:@"%@&%@&p=iOS", request.URL.absoluteString, append]]; } else { url = [NSURL URLWithString:[NSStringstringWithFormat:@"%@?%@&p=iOS", request.URL.absoluteString, append]]; } NSMutableURLRequest *newRequest = [request mutableCopy]; newRequest.URL = url; next(newRequest, wrappedResponse); }
DelegatingHandlers are Service Filters public static MobileServiceClientMobileService = new MobileServiceClient( "https://<your subdomain>.azure-mobile.net/", "<your app key>", new VersionHandler() ); using System; using System.Net.Http; using System.Threading.Tasks; namespace WindowsStore { public class VersionHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationTokencancellationToken) { request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.ToString() + "?version=v2"); return base.SendAsync(request, cancellationToken); } } }
Checking the Version in Scripts function insert(item, user, request) { if (request.parameters.build < 2.0) { item.description = 'Not entered'; } request.execute({ success : function() { if (request.parameters.build < 2.0) { delete item.description; } request.respond(); } }); }
For more on versioning, check outGoing Live and Beyond with Windows Azure Mobile ServicesFriday @ 10:30 am
Part 1: The Helpers function generateOAuthSignature(method, url, data){ var index = url.indexOf('?'); if (index > 0) url = url.substring(0, url.indexOf('?')); varsigningToken = encodeURIComponent('Your Consumer Secret') + "&" + encodeURIComponent('Your Access Token Secret'); var keys = []; for (var d in data){ if (d != 'oauth_signature') { console.log('data: ' , d); keys.push(d); } } keys.sort(); var output = "GET&" + encodeURIComponent(url) + "&"; varparams = ""; keys.forEach(function(k){ params += "&" + encodeURIComponent(k) + "=" + encodeURIComponent(data[k]); }); params = encodeURIComponent(params.substring(1)); return hashString(signingToken, output+params, "base64"); } function hashString(key, str, encoding){ varhmac = crypto.createHmac("sha1", key); hmac.update(str); return hmac.digest(encoding); } function generateNonce() { var code = ""; for (vari = 0; i < 20; i++) { code += Math.floor(Math.random() * 9).toString(); } return code; }
Part 2: The Work (part 1) var crypto = require('crypto'); varquerystring = require('querystring'); function read(query, user, request) { var result = { id: query.id, identities: user.getIdentities(), userName: '' }; var identities = user.getIdentities(); varuserId = user.userId; vartwitterId = userId.substring(userId.indexOf(':') + 1); //API 1.0 //url = 'https://api.twitter.com/1/users/show/' + twitterId + '.json'; //API 1.1 varurl = 'https://api.twitter.com/1.1/users/show.json?user_id=' + twitterId; var key = 'This is your consumer key'; var nonce = generateNonce(); varsigmethod = 'HMAC-SHA1'; var version = '1.0'; vartwitterAccessToken = identities.twitter.accessToken; varoauth_token = 'The Access Token'; var seconds = new Date() / 1000; seconds = Math.round(seconds); varrequestType = 'GET'; varoauthData = { oauth_consumer_key: key, oauth_nonce: nonce, oauth_signature:null, oauth_signature_method: sigmethod, oauth_timestamp: seconds, oauth_token: oauth_token, oauth_version: version }; varsigData = {}; for (var k in oauthData){ sigData[k] = oauthData[k]; } sigData['user_id'] = twitterId;
Part 2.2: The Work var sig = generateOAuthSignature('GET', url, sigData); oauthData.oauth_signature = sig; varoauthHeader = ""; for (k in oauthData){ oauthHeader += ", " + encodeURIComponent(k) + "=\"" + encodeURIComponent(oauthData[k]) + "\""; } oauthHeader = oauthHeader.substring(1); varauthHeader = 'OAuth' + oauthHeader; //Generate callback for response from Twitter API varrequestCallback = function (err, resp, body) { if (err || resp.statusCode !== 200) { console.error('Error sending data to the provider: ', err); request.respond(statusCodes.INTERNAL_SERVER_ERROR, body); } else { try { varuserData = JSON.parse(body); if (userData.name != null) result.UserName = userData.name; else result.UserName = "can't get username"; request.respond(200, [result]); } catch (ex) { console.error('Error parsing response from the provider API: ', ex); request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex); } } } //Create the request and execute it varreq = require('request'); varreqOptions = { uri: url, headers: { Accept: "application/json" } }; if (authHeader != null) reqOptions.headers['Authorization'] = authHeader; req(reqOptions, requestCallback); }