Introduction

This sample demonstrates how to integrate Windows Azure Mobile Service with line of business applications, running on-premises or in the cloud, via BizTalk Server 2013, Service Bus Brokered Messaging and Service Bus Relayed Messaging. The Access Control Service is used to authenticate Windows Azure Mobile Services against the Windows Azure Service Bus. In this scenario, BizTalk Server 2013 can run on-premises or in a Virtual Machine on Windows Azure. You can download the code from MSDN Code Gallery.

Scenario

This scenario extends the TodoList tutorial application. For more information, see the following resources:
A mobile service receives a new todo item in JSON format sent by an HTML5/JavaScript site, Windows Phone 8 or Windows Store app via HTTP POST method. The mobile service performs the following actions:
  • Authenticates the user against the Microsoft, Facebook, Twitter, Google identity providers using the OAuth open protocol. For more information on this topic, see Troubleshooting authentication issues in Azure Mobile Services by Carlos Figueira.
  • Validates the input data.
  • Uses the access token issued by the identity provider as a key to query via REST the authentication provider and retrieve the user name. For more information on this topic, see Getting user information on Azure Mobile Services by Carlos Figueira.
  • Retrieves the user address from BizTalk Server 2013 via Service Bus Relayed Messaging.
  • Saves the new item to the TodoItem table on the Windows Azure SQL Database of the mobile service.
  • Reads notification channels from the Channel table on the Windows Azure SQL Database of the mobile service.
  • Sends push notifications to Windows Store Apps using the Windows Push Notification Service (WNS).
  • Sends push notifications to Windows Phone 8 Apps using the Microsoft Push Notification Service (MPNS).
  • Sends a notification to BizTalk Server 2013 via Service Bus Brokered Messaging.

Architecture

The following diagram shows the architecture of the solution.
 
Message Flow
  1. The client application (HTML5/JavaScript site, Windows Phone 8 app or Windows Store app) sends authentication credentials to the mobile service.
  2. The mobile service redirects the user to the page of the select authentication provider which validates the credentials (username and password) provided by the user and issues a security token.
  3. The mobile service returns its access token to the client application. The user sends a new todo item to the mobile service.
  4. The insert script for the TodoItem table handles the incoming call. The script validates the inbound data then invokes the authentication provider via REST using the request module (getUserName function).
  5. The script sends a request to the Access Control Service to acquire a security token necessary to be authenticated by the Service Bus Relay Service exposed by BizTalk Server via a WCF-BasicHttpRelay Receive Location. The mobile service uses the OAuth WRAP Protocol to acquire a security token from ACS (getAcsToken funtion). In particular, the server script sends a request to ACS using the https module. The request contains the following information:
    • wrap_name: the name of a service identity within the Access Control namespace of the Service Bus Relay Service (e.g. owner)
    • wrap_password:  the password of the service identity specified by the wrap_name parameter.
    • wrap_scope: this parameter contains the relying party application realm. In our case, it contains the http base address of the Service Bus Relay Service (e.g. http://paolosalvatori.servicebus.windows.net/)
    ACS issues and returns a security token. For more information on the OAuth WRAP Protocol, see How to: Request a Token from ACS via the OAuth WRAP Protocol. The insert script calls the getUserAddress function that performs the following actions:
    • Extracts the wrap_access_token from the security token issued by ACS.
    • Creates a SOAP envelope to invoke the Service Bus Relay Service. In particular, the Header contains a RelayAccessToken element which in turn contains the wrap_access_token returned by ACS in base64 format. The Body contains the payload for the call.
    • Uses the request module to send the SOAP envelope to the Service Bus Relay Service. The Service Bus Relay Service validates and remove the security token, then forwards the request to BizTalk Server that processes the request and returns a response message containing the user address. See below for more details on his use case.
  6. The insert script calls the insertItem function that inserts the new item in the TodoItem table.
  7. The insert script retrieves from the Channel table the channel URI of the Windows Phone 8 and Windows Store apps to which to send push a notification (sendPushNotification function)
  8. The sendPushNotification function sends push notifications.
  9. The insert script calls the sendMessageToServiceBus function that uses the azure module to send a notification to BizTalk Server via a Windows Azure Service Bus queue.

    Call BizTalk Server via Service Bus Relayed Messaging

    The following diagram shows how BizTalk Server is configured to receive and process request messages sent by a mobile service via Service Bus Relay Service using a two-way request-reply message exchange pattern.
    Message Flow 
    1. The client application sends a new item to the mobile service.
    2. The insert script sends a request to the Access Control Service to acquire a security token necessary to be authenticated by the Service Bus Relay Service exposed by BizTalk Server via a WCF-BasicHttpRelay Receive Location. The mobile service uses the OAuth WRAP Protocol to acquire a security token from ACS (getAcsToken funtion). In particular, the server script sends a request to ACS using the https module. The request contains the following information:
      • wrap_name: the name of a service identity within the Access Control namespace of the Service Bus Relay Service (e.g. owner)
      • wrap_password:  the password of the service identity specified by the wrap_name parameter.
      • wrap_scope: this parameter contains the relying party application realm. In our case, it contains the http base address of the Service Bus Relay Service (e.g. http://paolosalvatori.servicebus.windows.net/).
    3. ACS issues and returns a security token. For more information on the OAuth WRAP Protocol, see How to: Request a Token from ACS via the OAuth WRAP Protocol.
    4. The insert script calls the getUserAddress function that performs the following actions:
      • Extracts the wrap_access_token from the security token issued by ACS.
      • Creates a SOAP envelope to invoke the Service Bus Relay Service. In particular, the Header contains a RelayAccessToken element which in turn contains the wrap_access_token returned by ACS in base64 format. The Body contains the payload for the call.
      • Uses the request module to send the SOAP envelope to the Service Bus Relay Service. The Service Bus Relay Service validates and remove the security token, then forwards the request to BizTalk Server that processes the request and returns a response message containing the user address. See below for more details on his use case.
    5. The Service Bus Relay Service validates and remove the security token, then forwards the request to one the WCF-BasicHttpRelay Receive Location exposed by BizTalk Server.
    6. The WCF-BasicHttpRelay Receive Location publishes the request message to the BizTalkServerMsgBoxDb.
    7. The message triggers the execution of a new instance of the GetUserAddress orchestration. 
    8. The orchestration uses the user id contained in the request message to retrieve his/her address. For demo purpose, the orchestration generates a random address. The orchestration writes the response message to the BizTalkServerMsgBoxDb.
    9. The WCF-BasicHttpRelay Receive Location retrieves the message from the BizTalkServerMsgBoxDb.
    10. The receive location sends the response message back to the Service Bus Relay Service.
    11. The Service Bus Relay Service forwards the message to the mobile service.
    12. The mobile service saves the new item in the TodoItem table and sends the enriched item back to the client application.

     

    Call BizTalk Server via Service Bus Brokered Messaging

    The following diagram shows how BizTalk Server is configured to receive and process request messages sent by a mobile service via a Service Bus queue using a one-way message exchange pattern.
    Message Flow
    1. The client application sends a new item to the mobile service.
    2. The insert script calls the sendMessageToServiceBus function that performs the following actions: 
      • Creates a XML message using the xmlbuilder module.
      • Uses the azure to send the message to Windows Azure Service Bus queue.
    3. BizTalk Server 2013 uses a SB-Messaging Receive Location to retrieve the message from the queue.
    4. The WCF-BasicHttpRelay Receive Location publishes the notification message to the BizTalkServerMsgBoxDb.
    5. The message triggers the execution of a new instance of the StoreTodoItem orchestration.
    6. The orchestration elaborates and transforms the incoming message and publish a new message to the BizTalkServerMsgBoxDb.
    7. The message is consumed by a FILE Send Port.
    8. The FILE Send Port writes the message to the Out folder.

    Prerequisites

    Building the Sample

    Proceed as follows to set up the solution.

    Create the Todo Mobile Service

    Follow the steps in the tutorial to create the Todo mobile service.
    1. Log into the Management Portal.
    2. At the bottom of the navigation pane, click +NEW.

    3. Expand Mobile Service, then click Create.
       
      This displays the New Mobile Service dialog.
    4. In the Create a mobile service page, type a subdomain name for the new mobile service in the URL textbox and wait for name verification. Once name verification completes, click the right arrow button to go to the next page.
       
      This displays the Specify database settings page.
      Note: As part of this tutorial, you create a new SQL Database instance and server. You can reuse this new database and administer it as you would any other SQL Database instance. If you already have a database in the same region as the new mobile service, you can instead choose Use existing Database and then select that database. The use of a database in a different region is not recommended because of additional bandwidth costs and higher latencies.
    5. In Name, type the name of the new database, then type Login name, which is the administrator login name for the new SQL Database server, type and confirm the password, and click the check button to complete the process.

    Configure the application to authenticate users

    This solution requires the user to be authenticated by an identity providers. Follow the instructions contained in the links below to configure the Mobile Service to authenticate users against one or more identity providers and follow the steps to register your app with that provider: For more information, see:
    For your convenience, here you find the steps to configure the application to authanticate users using a Microsoft Account login.
    1. Log on to the Windows Azure Management Portal, click Mobile Services, and then click your mobile service.

    2. Click the Dashboard tab and make a note of the Site URL value.
    3. Navigate to the My Applications page in the Live Connect Developer Center, and log on with your Microsoft account, if required.
    4. Click Create application, then type an Application name and click I accept.
      This registers the application with Live Connect.
    5. Click Application settings page, then API Settings and make a note of the values of the Client ID and Client secret.
      Security Note
      The client secret is an important security credential. Do not share the client secret with anyone or distribute it with your app.
    6. In Redirect domain, enter the URL of your mobile service, and then click Save.
    7. Back in the Management Portal, click the Identity tab, enter the Client Id and Client Secret obtained at the previous step in the microsoft account settings, and click Save.

     Restrict permissions to authenticated users

    1. In the Management Portal, click the Data tab, and then click the TodoItem table.
    2. Click the Permissions tab, set all permissions to Only authenticated users, and then click Save. This will ensure that all operations against the TodoItem table require an authenticated user. This also simplifies the scripts in the next tutorial because they will not have to allow for the possibility of anonymous users.

    Define server side scripts

    Server scripts are registered in a mobile service and can be used to perform a wide range of operations on data being inserted and updated, including validation and data modification. In this sample, they are used to validate data, retrieves data from identity providers, send push notifications and communicate with BizTalk Server via Windows Azure Service Bus. For more information on server scripts, see the following resources:
    To use Windows Azure Service Bus, you need to use the Node.js azure package in server scripts. This package includes a set of convenience libraries that communicate with the storage REST services. For more information on the Node.js azure package, see the following resources:
    Follow these steps to create server scripts:
    1. In the Management Portal, click the Data tab, and then click the TodoItem table.
        

        
    2. Click the scripts tab and select the insert, update, read or del script from the drow-down list.
        

        
    3. Modify the code of the selected script to add your business logic to the function.

    TodoItem Insert Script

    The following table contains the code for the insert script for the TodoItem table.
     
    // Variables
    var namespace = '[YOUR-SERVICE-BUS-NAMESPACE]';
    var issuerName = '[YOUR-ISSUER-NAME]';
    var issuerSecret = '[YOUR-ISSUER-SECRET]';
    
    function insert(item, user, request) {   
        
    
      // Validation
      if (!item.text) {
        request.respond(statusCodes.BAD_REQUEST, "The text cannot be null");
        return;
      }
      
      // Check for duplicates
      var todoItemTable = tables.getTable("TodoItem");
      todoItemTable.where({userId: user.userId, 
                 text: item.text,
                 complete: false}).
                 read({
                   success: function(results) {
                     if (results.length > 0) {
                       console.log("[todoItem.insert]: Duplicate detected: userId = [" + 
                             user.userId +
                             "] text = [" + 
                             item.text + "]")
                       request.respond(statusCodes.OK, results[0]);
                     }
                     else {
                      getUserName();
                     }   
                   }
                 });
                 
      function insertItem () {
        // Enrich the item with userId and date
        item.userId = user.userId || "Unknown";
        item.createdAt = new Date();
        
        // Execute insert request
        console.log("[todoItem.insert.insertItem]: About to insert:", item);
        request.execute({
          success: function() {
            console.log("[todoItem.insert.insertItem]: Successfully inserted: ", item);
            sendMessageToServiceBus();
            sendPushNotification();
            request.respond();
          },
          error: function(error) {
            console.error("[todoItem.insert.insertItem]: Error: ", error);
            request.respond();
          }
        });
      }
      
      function sendPushNotification() {
      var channelTable = tables.getTable('Channel');
        channelTable.read({
          success: function(channels) {
            channels.forEach(function(channel) {
              try {
                if (channel.type === "WNS") {
                  push.wns.sendToastText02(channel.uri, {
                    text1: "New Todo Item",
                    text2: item.text
                  }, {
                    success: function(pushResponse) {
                      console.log("[todoItem.insert.sendPushNotification]: Sent push notification:", pushResponse);
                    }
                  });
                }
                if (channel.type === "MPNS") {
                  push.mpns.sendFlipTile(channel.uri, {
                    title: item.text
                  }, {
                    success: function(pushResponse) {
                      console.log("[todoItem.insert.sendPushNotification]: Sent push notification:", pushResponse);
                    }
                  });
                  push.mpns.sendToast(channel.uri, {
                     text1: 'TodoList',
                     text2: item.text
                  }, {
                    success: function(pushResponse) {
                      console.log("[todoItem.insert.sendPushNotification]: Sent push notification:", pushResponse);
                    }
                  });
                }  
              }
              catch (exception) {
                console.warn('[todoItem.insert.sendPushNotification]: ', exception);
              }             
            });
          }
        });
      }
      
      function sendMessageToServiceBus() { 
        // Import the azure module
        var azure = require('azure');
    
        // Create a ServiceBusService object
            var serviceBusService = azure.createServiceBusService(namespace, 
                                        issuerSecret); 
    
        // Define queue options
        var queueOptions = {
          EnableBatchedOperations: true,
          RequiresDuplicateDetection : true,
          DuplicateDetectionHistoryTimeWindow: 'PT8H'
        };
    
        // Create queue if it does not exist
            serviceBusService.createQueueIfNotExists('mobileservices/todoitem', queueOptions, function(error) { 
                if (error) {
      console.error("[todoItem.insert.sendMessageToServiceBus]: An error occurred while accessing the Service Bus queue: ", error);
                }
                else { 
                    var builder = require('xmlbuilder');
                    var root = builder.create('todoItem',
                                {'version': '1.0', 'encoding': 'UTF-8'})
                                .att('xmlns', 'http://windowsazure.cat.microsoft.com/samples/mobileservices');
                    root.ele('userId', user.userId);
            root.ele('text', item.text);
                    root.ele('complete', item.complete.toString());
            
            // Create a message
            var message = {
              body: root.end({ 'pretty': true, 'indent': '  ', 'newline': '\n' }),
              messageId: item.id,
              customProperties: {
                source: 'Mobile Services',
                author: 'Paolo Salvatori'
              }
            };
    
            // Send the message to the queue
                    serviceBusService.sendQueueMessage('mobileservices/todoitem', message, function(error) { 
                        if (!error) { 
                            console.log("[todoItem.insert.sendMessageToServiceBus]: Sent message: %j", message); 
                        } 
              else {
       console.error("[todoItem.insert.sendMessageToServiceBus]: An error occurred while sending a message to the Service Bus: ", error);
              }
                    }); 
                } 
            }); 
        }
      
      function getUserName (callback) {
        item.userName = '<unknown>';
        var identities = user.getIdentities();
        var req = require('request');
        var options;
        
        if (identities.microsoft) {
          options = {
            'uri': 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + identities.microsoft.accessToken,
            'headers': { Accept: "application/json" }
            };
        } else if (identities.facebook) {
          options = {
            'uri': 'https://graph.facebook.com/me?access_token=' + identities.facebook.accessToken,
            'headers': { Accept: "application/json" }
            };
        } else if (identities.google) {
          options = {
            'uri': 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' + identities.google.accessToken,
            'headers': { Accept: "application/json" }
            };
        } else if (identities.twitter) {
          options = {
            'uri': 'https://api.twitter.com/1.1/users/show.json?user_id=' + 
              user.userId.substring(user.userId.indexOf(':') + 1),
            'headers': { 
              'Accept': "application/json", 
              'Authorization': 'OAuth ' + 
                       'oauth_consumer_key="bzgqwiHwd376EbMH0xLQw", ' +
                       'oauth_nonce="ab818809f50a865b0dd22ba8f998e770", ' +
                       'oauth_signature="seVa%2BV%2BAgepg9K7SqdY6BHgpbbQ%3D", ' + 
                       'oauth_signature_method="HMAC-SHA1", ' + 
                       'oauth_timestamp="' + Math.floor(new Date().getTime() / 1000) + '", ' +
                       'oauth_token="326170324-pp7mMfEGfN7yIjixQlsZlZDWh9xr8iO7n4QjJ4Zc", ' + 
                       'oauth_version="1.0"' 
              }
            };
        }
        console.log('[todoItem.insert.getUserName]: Authentication Provider URL: ', options.uri);
        if (options) {
          req(options, function(error, response, body) {
            console.log('Response: ', response);
            console.log('Body: ', body);
            if (error)
            {
              console.error('[todoItem.insert.getUserName]: An error occurred while requesting data to the identity provider: ', error);
            }
            else {
              try
              {
                var data = JSON.parse(body);
                item.userName = data.name ? data.name : '<unknown>';
              }
              catch (ex) {
    console.error('[todoItem.insert.getUserName]: An error occurred while parsing response data from the authentication provider: ', ex);
              }
            }
            getAcsToken();
          });
        }
        else {
          getAcsToken();
        }
      }
      
        function getAcsToken() {
        var https = require('https');
        var util = require('util');
        var qs = require('querystring');
        
            var options = {
                host: namespace + '-sb.accesscontrol.windows.net',
                path: '/WRAPv0.9/',
                method: 'POST'
            };
    
            var values = {
                wrap_name: issuerName,
                wrap_password: issuerSecret,
                wrap_scope: 'http://' + namespace + '.servicebus.windows.net/'
            };
    
            console.log('[getAcsToken]: options: %j\n\rrequest values: %j', options, values);
    
            var req = https.request(options, function (res) {
                console.log("[getAcsToken]: statusCode: %d\n\rheaders: %j", res.statusCode, res.headers);
                res.on('data', function (data) {
                    var json = qs.parse(data.toString('utf8'));
                    if (res.statusCode == 200) {
                        if (json.hasOwnProperty('wrap_access_token')) {
                var token = json.wrap_access_token;
                            getUserAddress(token);
                        }
                        else {
                            request.respond(statusCodes.BAD_REQUEST, "[getAcsToken]: ACS didn't return a valid token.");
                        }
                    }
                    else {
                        request.respond(res.statusCode, util.format('[getAcsToken]: %s', data));
                    }
                });
            });
            req.write(qs.stringify(values));
            req.end();
    
            req.on('error', function (e) {
                console.error('[getAcsToken]: ', e);
                request.respond(statusCodes.BAD_REQUEST, util.format('[getAcsToken]: %j', e));
            });
        }
    
      function getUserAddress(token) {
        // Set default value
        item.userAddress = '<unknown>';
        
            // Turn authorization token to base64 format
            var base64token = new Buffer(token).toString('base64');
    
        // Create the SOAP Envelope
         var body = '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">' +
                '<s:Header>' +
                '<RelayAccessToken xmlns="http://schemas.microsoft.com/netservices/2009/05/servicebus/connect">' +
                '<wsse:BinarySecurityToken wsu:Id="uuid:b7ddf730-61cc-49b4-abc4-7019dc2f9e86" ' +
                'ValueType="http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0" ' +
                'EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ' + 
                'xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" ' +
                'xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' + 
                base64token + 
                '</wsse:BinarySecurityToken>' +
                '</RelayAccessToken>' +
                '</s:Header>' +
          '<s:Body>' +
          '<user xmlns="http://windowsazure.cat.microsoft.com/samples/mobileservices">' +
          '<userId>' + user.userId + '</userId>' +
          '</user>' +
          '</s:Body>' +
          '</s:Envelope>';
          
        // Import the request module
        var req = require('request');
        
        // Create call options
        var options = {
          method: 'POST',
          url: 'https://' + namespace + '.servicebus.windows.net/basichttp/todoitem',
          headers: {
            'Content-Type': 'text/xml',
            'SOAPAction': 'GetUserAddress'
          },
          body: body
        };
        
        // Call the Service Bus Relay Service
        req(options, function(error, response, body) {
          if (error) {
            console.log('[todoItem.insert.getUserAddress]: An error occurred while invoking the service: ', error);
          } else {
            console.log('[todoItem.insert.getUserAddress]: response in XML: ', body);
            
            // Import the xml2js module
            var xml2js = require('xml2js');
            
            // Create a xml2js parser
            var parser = new xml2js.Parser();
            
            // Parse the SOAP envelope of the response from XML to JSON
            parser.parseString(body, function (err, result) {
              console.log('[todoItem.insert.getUserAddress]: response in JSON: ', JSON.stringify(result));
              try {
                // Read the address from the JSON object
                var address = result["s:Envelope"]["s:Body"][0]["userAddress"][0]["text"][0];
                if (address) {
                  // Set the userAddress property of the item
                  item.userAddress = address;
                }
              }
              catch (ex) {
                // Log the error
                console.log("[todoItem.insert.getUserAddress]: An error occurred while parsing the response: ", error);
              }
            });
            insertItem();
          }
        });
      }
    }
    NOTE: make sure to replace the following placeholders in the upper part od the code before saving the script:    
    • [YOUR-SERVICE-BUS-NAMESPACE]: specify the name of your Windows Azure Service Bus namespace.
    • [YOUR-ISSUER-NAME]: specify the name of a service identity used to access the Service Bus Relay Service (e.g. owner)
    • [YOUR-ISSUER-SECRET]: specify the password for the selected service identity.

    TodoItem Read Script

    The following table contains the code for the read script for the TodoItem table.
    function read(query, user, request) {
        query.where({ userId: user.userId });    
        request.execute();
    }

    Channel Insert Script

    The following table contains the code for the insert script for the Channel table.
    function insert(item, user, request) {
       var channelTable = tables.getTable('Channel');
        channelTable
            .where({ uri: item.uri })
            .read({ success: insertChannelIfNotFound });
        function insertChannelIfNotFound(existingChannels) {
            if (existingChannels.length > 0) {
                request.respond(200, existingChannels[0]);
            } else {
                request.execute();
            }
        }
    }
         

    Configure Git Source Control

    The source control support provides a Git repository as part your mobile service, and it includes all of your existing Mobile Service scripts and permissions. You can clone that git repository on your local machine, make changes to any of your scripts, and then easily deploy the mobile service to production using Git. This enables a really great developer workflow that works on any developer machine (Windows, Mac and Linux). To configure the Git source control proceed as follows:
    1. Navigate to the dashboard for your mobile service and select the Set up source control link:
    2. If this is your first time enabling Git within Windows Azure, you will be prompted to enter the credentials you want to use to access the repository:
    3. Once you configure this, you can switch to the CONFIGURE tab of your Mobile Service and you will see a Git URL you can use to use your repository:
    You can use the GIT URL to clone the repository locally using Git from the command line:
    cd C:\  
    mkdir Git  
    cd Git  
    git clone https://todolist.scm.azure-mobile.net/todolist.git
     
    You can make changes to the code of server scripts and then upload changes to your mobile service using the following script.
    git add -A . 
    git commit -m "Modified calculator server script" 
    git push

    Visual Studio Solution

    The Visual Studio solution includes the following projects:
    • BusinessLogic: contains helper classes used by BizTalk Server orchestrations.
    • Orchestrations: contains two orchestrations:
      • GetUserAddress
      • StoreTodoItem
    • Schemas: contains XML schemas for the messages exchanged by BizTalk Server with the Mobile Service via Windows Azure Service Bus.
    • Maps: contains the maps uses by the BizTalk Server application.
    • HTML5: contains the HTML5/JavaScript client for the mobile service.
    • WindowsPhone8: contains the Windows Phone 8 app that can be used to test the mobile service.
    • WindowsStoreApp: contains the Windows Store app that can be used to test the mobile service.

    NOTE: the WindowsStoreApp project uses the Windows Azure Mobile Services NuGet package. To recuce the size of tha zip file, I deleted some of the asemblies from the packages folder. To repair the solution, make sure to right click the solution and select Enable NuGet Package Restore as shown in the picture below. For more information on this topic, see the following post.

    BizTalk Server Application

    Proceed as follows to create the TodoItem BizTalk Server application:
    • Open the solution in Visual Studio 2012 and deploy the Schemas, Maps and Orchestration to create the TodoItem application.
    • Open the Binding.xml file in the Setup folder and replace the [YOUR-SERVICE-BUS-NAMESPACE] placeholder with the name of your Windows Azure Service Bus namespace. 
    • Open the BizTalk Server Administration Console and import the binding file to ceate Receive Ports, Receive Locations and Send Ports.
    • Open the WCF-BasicHttpRelay Receive Location, click the Configure button:
        
    • Click the Edit button in the Access Control Service section under the Security tab.
        
    • Define the ACS STS uri, Issuer Name and Issuer Secret:
        
    • Open the SB-Messaging Receive Location, click the Configure button:
        
    • Define the ACS STS uri, Issuer Name and Issuer Secret under the Authentication tab:
        
    • Open the FILE Send Port and click the Configure button:
    • Enter the path of the Destination folder where notification messages sent by th Mobile Service via Service Bus are stored:   

    HTML5/JavaScript Client

    The following figure shows the HTML5/JavaScript application that you can use to test the mobile service.

    The following table contains the code the app.js script that contains the client-side logic used by the application to invoke the mobile service.

    $(function() {
      var client = new WindowsAzure.MobileServiceClient('https://[YOUR-MOBILE-SERVICE-NAME].azure-mobile.net/', 
    '[YOUR-MOBILE-SERVICE-APPLICATION-KEY]'), todoItemTable = client.getTable('todoitem'); // Read current data and rebuild UI. // If you plan to generate complex UIs like this, consider using a JavaScript templating library. function refreshTodoItems() { var query = todoItemTable.where({ complete: false }); query.read().then(function(todoItems) { var listItems = $.map(todoItems, function(item) { return $('<li>') .attr('data-todoitem-id', item.id) .append($('<button class="item-delete">Delete</button>')) .append($('<input type="checkbox" class="item-complete">').prop('checked', item.complete)) .append($('<div>').append($('<input class="item-text">').val(item.text))); }); $('#todo-items').empty().append(listItems).toggle(listItems.length > 0); $('#summary').html('<strong>' + todoItems.length + '</strong> item(s)'); }); } function getTodoItemId(formElement) { return Number($(formElement).closest('li').attr('data-todoitem-id')); } // Handle insert $('#add-item').submit(function(evt) { var textbox = $('#new-item-text'), itemText = textbox.val(); if (itemText !== '') { todoItemTable.insert({ text: itemText, complete: false }).then(refreshTodoItems); } textbox.val('').focus(); evt.preventDefault(); }); // Handle update $(document.body).on('change', '.item-text', function() { var newText = $(this).val(); todoItemTable.update({ id: getTodoItemId(this), text: newText }); }); $(document.body).on('change', '.item-complete', function() { var isComplete = $(this).prop('checked'); todoItemTable.update({ id: getTodoItemId(this), complete: isComplete }).then(refreshTodoItems); }); // Handle delete $(document.body).on('click', '.item-delete', function () { todoItemTable.del({ id: getTodoItemId(this) }).then(refreshTodoItems); }); // On initial load, start by fetching the current data // refreshTodoItems(); function refreshAuthDisplay() { var isLoggedIn = client.currentUser !== null; $("#logged-in").toggle(isLoggedIn); $("#logged-out").toggle(!isLoggedIn); if (isLoggedIn) { $("#login-name").text(client.currentUser.userId); refreshTodoItems(); } else { $('#todo-items').empty(); } } function logIn() { client.login($("#authentication-providers :selected").val()).done(refreshAuthDisplay, function (error) { alert(error); }); } function logOut() { client.logout(); refreshAuthDisplay(); $('#summary').html('<strong>You must login to access data.</strong>'); } // On page init, fetch the data and set up event handlers $(function () { refreshAuthDisplay(); $('#summary').html('<strong>You must login to access data.</strong>'); $("#logged-out button").click(logIn); $("#logged-in button").click(logOut); }); });

    NOTE: make sure to replace the following placeholders in the app.js and index.html files:

    • [YOUR-MOBILE-SERVICE-NAME]: specify the name of your mobile service.
    • [YOUR-MOBILE-SERVICE-APPLICATION-KEY]: specify the application key of your mobile service.

     

    Windows Store App

    The following figure shows the Windows Store App that you can use to test the mobile service.
    The following table contains the code of the MainPage.xaml.cs class that contains the client-side logic used to invoke the mobile service.
    using System;
    using Microsoft.WindowsAzure.MobileServices;
    using Newtonsoft.Json;
    using Windows.Networking.PushNotifications;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Navigation;
    using Windows.UI.Popups;
    using System.Linq;
    
    namespace babo
    {  
      public class TodoItem
      {
        public int Id { get; set; }
    
        [JsonProperty(PropertyName = "text")]
        public string Text { get; set; }
    
        [JsonProperty(PropertyName = "complete")]
        public bool Complete { get; set; }
      }
    
      public class Channel
      {
        public int Id { get; set; }
    
        [JsonProperty(PropertyName = "uri")]
        public string Uri { get; set; }
    
        [JsonProperty(PropertyName = "type")]
        public string Type { get; set; }
      }
    
      public sealed partial class MainPage
      {
        // MobileServiceCollectionView implements ICollectionView (useful for databinding to lists) and 
        // is integrated with your Mobile Service to make it easy to bind your data to the ListView
        private MobileServiceCollection<TodoItem, TodoItem> items;
        private readonly IMobileServiceTable<TodoItem> todoTable = App.MobileService.GetTable<TodoItem>();
    
        public MainPage()
        {
          InitializeComponent();
        }
    
        //private LiveConnectSession session;
    
        private async System.Threading.Tasks.Task Authenticate()
        {
          try
          {
            var authenticationProvider = MobileServiceAuthenticationProvider.MicrosoftAccount;
            switch (AuthenticationProvider.SelectedValue as string)
            {
              case "Microsoft":
                authenticationProvider = MobileServiceAuthenticationProvider.MicrosoftAccount;
                break;
              case "Facebook":
                authenticationProvider = MobileServiceAuthenticationProvider.Facebook;
                break;
              case "Twitter":
                authenticationProvider = MobileServiceAuthenticationProvider.Twitter;
                break;
              case "Google":
                authenticationProvider = MobileServiceAuthenticationProvider.Google;
                break;
            }
            await App.MobileService.LoginAsync(authenticationProvider);
            ShowStatusAsync(string.Format("User successfully authenticated: UserId = {0}",
                        App.MobileService.CurrentUser.UserId));
            TextInput.IsEnabled = true;
            ButtonSave.IsEnabled = true;
            ButtonRefresh.IsEnabled = true;
          }
          catch (InvalidOperationException)
          {
            ShowStatusAsync("Login failed!");
          }
        }
    
        private async void InsertTodoItem(TodoItem todoItem)
        {
          // This code inserts a new TodoItem into the database. When the operation completes
          // and Mobile Services has assigned an Id, the item is added to the CollectionView
          if (string.IsNullOrEmpty(todoItem.Text))
          {
            return;
          }
          await todoTable.InsertAsync(todoItem);
          //await todoTable.InsertAsync(new JsonObject{{"text", JsonValue.CreateStringValue(todoItem.Text)}, 
                                 //{"complete", JsonValue.CreateBooleanValue(todoItem.Complete)}});
          // Add the new item if no duplicates exist in the list
          if (!items.Any(i => i != null && 
                    !string.IsNullOrEmpty(i.Text) && 
                    string.Compare(i.Text, todoItem.Text, StringComparison.CurrentCultureIgnoreCase) == 0))
          {
            items.Add(todoItem); 
          }   
        }
    
        private async void RefreshTodoItems()
        {
          try
          {
            // This code refreshes the entries in the list view be querying the TodoItems table.
            // The query excludes completed TodoItems
            items = await todoTable.Where(todoItem => todoItem.Complete == false).ToCollectionAsync();
            ListItems.ItemsSource = items;
           }
          catch (InvalidOperationException)
          {
            ShowStatusAsync("Refresh items failed!");
          }
        }
    
        private async void UpdateCheckedTodoItem(TodoItem item)
        {
          // This code takes a freshly completed TodoItem and updates the database. When the MobileService 
          // responds, the item is removed from the list 
          await todoTable.UpdateAsync(item);
          items.Remove(item);
        }
    
        private void ButtonRefresh_Click(object sender, RoutedEventArgs e)
        {
          RefreshTodoItems();
        }
    
        private void ButtonSave_Click(object sender, RoutedEventArgs e)
        {
          var todoItem = new TodoItem { Text = TextInput.Text };
          InsertTodoItem(todoItem);
        }
    
        private void CheckBoxComplete_Checked(object sender, RoutedEventArgs e)
        {
          var cb = (CheckBox)sender;
          var item = cb.DataContext as TodoItem;
          UpdateCheckedTodoItem(item);
        }
    
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
          await AcquirePushChannel();
        }
    
        private async System.Threading.Tasks.Task AcquirePushChannel()
        {
          string message = null;
          try
          {
            var currentChannel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
            var uriAsString = currentChannel.Uri;
            var channelTable = App.MobileService.GetTable<Channel>();
            var enumerable = await channelTable.Where(c => c.Uri == uriAsString).ToEnumerableAsync();
            if (enumerable.Any())
            {
              return;
            }
            var channel = new Channel { Uri = currentChannel.Uri, Type = "WNS" };
            await channelTable.InsertAsync(channel);
          }
          catch (Exception ex)
          {
            message = ex.Message;
          }
          if (!string.IsNullOrEmpty(message))
          {
            var messageDialog = new MessageDialog(message, "Notification Status");
            messageDialog.ShowAsync();
          }
        }
    
        private async void Login_Click(object sender, RoutedEventArgs e)
        {
          if (App.MobileService.CurrentUser == null)
          {
            await Authenticate();
            if (App.MobileService.CurrentUser != null)
            {
              RefreshTodoItems();
              ButtonLogin.Content = "Logout";
            }
          }
          else
          {
            App.MobileService.Logout();
            ButtonLogin.Content = "Login";
          }
        }
    
        private async void ShowStatusAsync(string message, uint delay = 3)
        {
          if (string.IsNullOrEmpty(message))
          {
            return;
          }
          Status.Text = message;
          await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(delay));
          Status.Text = string.Empty;
        }
      }
    }
     NOTE: make sure to replace the following placeholders in the App.xaml.cs file:
    • [YOUR-MOBILE-SERVICE-NAME]: specify the name of your mobile service.
    • [YOUR-MOBILE-SERVICE-APPLICATION-KEY]: specify the application key of your mobile service.

     

    Windows Phone 8

    The following figure shows the Windows Phone 8 application that you can use to test the mobile service.
    The following table contains the code of the MainPage.xaml.cs class that contains the client-side logic used to invoke the mobile service.
    using Microsoft.WindowsAzure.MobileServices;
    using Newtonsoft.Json;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Navigation;
    
    namespace babo
    {
      public class TodoItem
      {
        public int Id { get; set; }
    
        [JsonProperty(PropertyName = "text")]
        public string Text { get; set; }
    
        [JsonProperty(PropertyName = "complete")]
        public bool Complete { get; set; }
      }
    
      public partial class MainPage
      {
        // MobileServiceCollectionView implements ICollectionView (useful for databinding to lists) and 
        // is integrated with your Mobile Service to make it easy to bind your data to the ListView
        private MobileServiceCollection<TodoItem, TodoItem> items;
    
        private readonly IMobileServiceTable<TodoItem> todoTable = App.MobileService.GetTable<TodoItem>();
    
        // Constructor
        public MainPage()
        {
          InitializeComponent();
        }
    
        private async void InsertTodoItem(TodoItem todoItem)
        {
          // This code inserts a new TodoItem into the database. When the operation completes
          // and Mobile Services has assigned an Id, the item is added to the CollectionView
          await todoTable.InsertAsync(todoItem);
          items.Add(todoItem);
        }
    
        private async void RefreshTodoItems()
        {
          // This code refreshes the entries in the list view be querying the TodoItems table.
          // The query excludes completed TodoItems
          try
          {
            items = await todoTable
              .Where(todoItem => todoItem.Complete == false)
              .ToCollectionAsync();
          }
          catch (MobileServiceInvalidOperationException e)
          {
            MessageBox.Show(e.Message, "Error loading items", MessageBoxButton.OK);
          }
    
          ListItems.ItemsSource = items;
        }
    
        private async void UpdateCheckedTodoItem(TodoItem item)
        {
          // This code takes a freshly completed TodoItem and updates the database. When the MobileService 
          // responds, the item is removed from the list 
          await todoTable.UpdateAsync(item);
          items.Remove(item);
        }
    
        private void ButtonRefresh_Click(object sender, RoutedEventArgs e)
        {
          RefreshTodoItems();
        }
    
        private void ButtonSave_Click(object sender, RoutedEventArgs e)
        {
          var todoItem = new TodoItem { Text = TodoInput.Text };
          InsertTodoItem(todoItem);
        }
    
        private void CheckBoxComplete_Checked(object sender, RoutedEventArgs e)
        {
          var cb = (CheckBox)sender;
          var item = cb.DataContext as TodoItem;
          if (item != null)
          {
            item.Complete = true;
            UpdateCheckedTodoItem(item);
          }
        }
    
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
          RefreshTodoItems();
        }
      }
    }
         
    NOTE: make sure to replace the following placeholders in the App.xaml.cs file: 
    • [YOUR-MOBILE-SERVICE-NAME]: specify the name of your mobile service.
    • [YOUR-MOBILE-SERVICE-APPLICATION-KEY]: specify the application key of your mobile service.

     

    Conclusions

    Mobile services can easily be extended to get advantage of the services provided by Windows Azure. demonstrates how to integrate Windows Azure Mobile Service with line of business applications, running on-premises or in the cloud, via BizTalk Server 2013, Service Bus Brokered Messaging and Service Bus Relayed Messaging. The Access Control Service is used to authenticate Windows Azure Mobile Services against the Windows Azure Service Bus. In this scenario, BizTalk Server 2013 can run on-premises or in a Virtual Machine on Windows Azure. You can download the code from MSDN Code Gallery. See also the following articles on Windows Azure Mobile Services: