Introduction

This sample demonstrates how to integrate Windows Azure Mobile Service with line of business applications, running on-premises or in the cloud, via Windows Azure BizTalk Services and Service Bus Relayed Messaging. The Access Control Service is used to authenticate Mobile Services against the XML Request-Reply Bridge used by the solution to transform and route messages to the line of business applications. You can download the code from MSDN Code Gallery.

Scenario

A mobile service receives a request message in JSON format sent by an HTML5/JavaScript site or a Windows Store app via HTTP POST method. The message contains an array of math operations (add, subtract, multiply, divide) and the location (EUROPE, US) of the backend service to which to send the request. The mobile service custom API acquires a security token from the Access Control Service to authenticate against the BizTalk Service that acts as a message broker and intermediary service towards the two services providers based in Europe and in the US. The server script creates the SOAP envelope for the request message, includes the security token in the HTTP Authorization request header and send the message to the runtime address of the underlying XML Request-Reply Bridge. The bridge transforms the message into the canonical request format expected by the two backend services located in Europe and in the US and
promotes the location element in the incoming message. Then it routes the message to the backend service located in Europe or in the US based on the content of the Location promoted property via Service Bus Relayed Messaging. Both the calculator services based in Europe or in the US expose three endpoints on the Windows Azure Service Bus. Each of these endpoints uses a different relay binding:

Likewise, the XMLRequest-Reply Bridge can be configured to use one of three client endpoints, each of which uses one of the above bindings. The calculator service serving the request processes the math operations contained in the inbound
message and returns results to the bridge in a SOAP response message. The bridge applies a map to transform the incoming message into the response format expected by the caller then it sends the resulting message back to the mobile service. The mobile service changes the format of the response message from SOAP/XML to JSON format, then extracts and sends the result data back to the client application.

Architecture

The following diagram shows the architecture of the solution.
   
Message Flow
  1. The HTML5/JavaScript web site or Windows Store app sends a request via HTTPS to the calculator user-defined custom API of a Windows Azure Mobile Service. The HTML5/JS application uses the invokeApi method exposed by the HTML5/JavaScript client for Windows Azure Mobile Services to call the mobile service. Likewise, the Windows Store app uses the InvokeApiAsync method exposed by the MobileServiceClient class. The custom API exposes a POST function which expects a request message in JSON format containing the following data:
    • Location: this parameter indicates the location of the calculator service (EUROPE, US).
    • Operations: an array of math operations (e.g. {"op": "+", "op1": "17", "op2": "52"}).
    Below a sample of a valid JSON request that you can send to the mobile service using a tool such as Fiddler.
     
    {"location": "EUROPE", 
     "operations": [{"op": "+", "op1": "17", "op2": "52"}, 
                    {"op": "-", "op1": "73", "op2": "31"}]}
    
    {"location": "EUROPE",  
     "operations": [{"op": "+", "op1": "17", "op2": "52"},  
                    {"op": "-", "op1": "73", "op2": "31"}]}
            
  2. The custom API sends a request to the Access Control Service to acquire a security token necessary to be authenticated by the underlying Service Bus Relay Service. The client uses the OAuth WRAP Protocol to acquire a security token from ACS. In particular, the server script sends a request to ACS using a HTTPS form POST. 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 mobile service user-defined custom API performs the following actions:
    • Extracts the wrap_access_token from the security token issued by ACS and uses its value to create the Authorization HTTP request header.
    • Creates a SOAP envelope to invoke the XML Request-Reply Bridge.
    • Uses the https Node.js module to send the SOAP envelope to the XML Request-Reply Bridge.
  5. The XML Request-Reply Bridge performs the following actions:
  6. The The XML Request-Reply Bridge uses the Route Rules and Route Ordering Table to route the request to one of the two backend services: If the value of the Location promoted property is equal to EUROPE the request is routed to the EUROPE Two-Way Relay Endpoint, otherwise is routed to the US Two-Way Relay Endpoint. This means that all requests with a location other than EUROPE are routed to the US calculator service. The configuration file of both the EUROPE and US Two-Way Relay Endpoints contains 3 client endpoints for each of the following relay bindings:

    Both the EUROPE and US Two-Way Relay Endpoints are configured to use the NetTcpRelayBinding client endpoint. For more information, see Including a Two-Way Relay Endpoint and Routing Messages from Bridges to Destinations in the BizTalk Service Project
  7. The selected Two-Way Relay Endpoint sends the request message to the corresponding Service Bus Relay Service.
  8. The Service Bus Relay Service validates and remove the security token, then forwards the request to one the listeners hosting the EUOPE or US calculator service.
  9. The calculator service processes the operations contained in the request message and returns a response message containing results to the Service Bus Relay Service. Both the calculator services based in EUROPE or in the US expose three endpoints on the Windows Azure Service Bus. Each of these endpoints uses a different relay binding:
  10. The Service Bus Relay Service forwards the message to the XML Request-Reply Bridge.
  11. The XML Request-Reply Bridge performs the following actions:
  12. The XML Request-Reply Bridge sends the response message to the mobile service.
  13. The custom API performs the following actions:
    • Uses the xml2js Node.js module to change the format of the response SOAP message from XML to JSON.
    • Flattens the resulting JSON object to eliminate unnecessary arrays.
    • Extracts data from the flattened representation of the SOAP envelope and creates a response message in JSON object.
    • Returns data in JSON format to the client application
NOTE: the mobile service communicates with client applications using a REST interface and messages in JSON format, while it communicates with the XML Request-Reply Bridge using SOAP messages.

Access Control Service

The following diagram shows the steps performed by a WCF service and client to communicate via a Service Bus Relay Service. The Service Bus uses a claims-based security model implemented using the Access Control Service (ACS). The service needs to authenticate against ACS and acquire a token with the Listen claim in order to be able to open an endpoint on the Service Bus. Likewise, when both the service and client are configured to use the RelayAccessToken authentication type, the client needs to acquire a security token from ACS containing the Send claim. When sending a request to the Service Bus Relay Service, the client needs to include the token in the RelayAccessToken element in the Header section of the request SOAP envelope. The Service Bus Relay Service validates the security token and then sends the message to the underlying WCF service. For more information on this topic, see How to: Configure a Service Bus Service Using a Configuration File.

Prerequisites

Building the Sample

Proceed as follows to set up the solution.
  

Create the BizTalk Service

Follow the steps described in BizTalk Services: Provisioning Using Windows Azure Management Portal to provision a new BizTalk Service on Windows Azure. After completing the provisioning process, you should see a new BizTalk Service on the Windows Azure Management Portal as shown by the following figure:

If you click the new BizTalk Service and then click Dashboard, you can access a page that provides a graph of commonly used performance metrics. You can use the Dashboard to perform a quick check of the health of your BizTalk Service, including failed messages and successful messages. Click the circle next to the metric to enable and disable the metric output in the graph.

In addition, you can click the Manage button at the bottom of the page to access the Windows Azure BizTalk Service Management Portal where you can manage your BizTalk Services. In particular, the Tracking section can be used to monitor the messages exchanged by bridges with external applications.

Create the Mobile Service

Follow the steps in the tutorial to create the 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. However, the database is not used by the present solution. Hence, 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.

Define the custom API

  1. Log into the Windows Azure Management Portal, click Mobile Services, and then click your app.
  2. Click the API tab, and then click Create a custom API. This displays the Create a new custom API dialog.
  3. Enter calculator in the API NAME field. Select Anybody with the Application Key permission for all the HTTP methods and then click the check button. This creates the new custom API.
  4. Click the new calcultor entry in the API table.
  5. Click the Scripts tab and replace the existing code with the following:
var bizTalkServiceNamespace = '[YOUR-BIZTALK-SERVICE-NAMESPACE]'; 
var bizTalkServiceUrl = bizTalkServiceNamespace + '.biztalk.windows.net'; 
var bridgePath = '/default/CalculatorService'; 
var bizTalkServiceScope = 'http://' + bizTalkServiceUrl + bridgePath; 
var acsNamespace = '[YOUR-BIZTALK-SERVICE-ACS-NAMESPACE]'; 
var issuerName = '[YOUR-BIZTALK-SERVICE-ACS-ISSUER-NAME]'; 
var issuerSecret = '[YOUR-BIZTALK-SERVICE-ACS-ISSUER-SECRET]'; 
var https = require('https'); 
var qs = require('querystring'); 
var util = require('util'); 

exports.post = function(request, response) {
  https.globalAgent.options.secureProtocol = 'SSLv3_method';
  https.globalAgent.options.rejectUnauthorized = false;

  getAcsToken(response,
        function(token) {
      callCalculatorBridge(token, request, response);
  });
};

function getAcsToken(response,
           callback) {
  var options = {
    host: acsNamespace + '.accesscontrol.windows.net',
    path: '/WRAPv0.9/',
    method: 'POST'
  };

  var values = {
    wrap_name: issuerName,
    wrap_password: issuerSecret,
    wrap_scope: bizTalkServiceScope
  };

  console.log('[getAcsToken]: options: %j\n\rrequest body: %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 body = qs.parse(data.toString('utf8'));
      if (res.statusCode == 200) {
        if (body.hasOwnProperty('wrap_access_token')) {
          var header = 'WRAP access_token=\"' + body.wrap_access_token + '\"';
          console.log('[getAcsToken]: header = %s', header);
          callback(header);
        }
        else {
          response.send(400, util.format("[getAcsToken]: ACS didn't return a valid token"));
        }
      }
      else {
        response.send(res.statusCode, util.format('[getAcsToken]: %s', data));
      }
    });
  });
  req.write(qs.stringify(values));
  req.end();

  req.on('error', function (e) {
    console.error('[getAcsToken]: ', e);
    response.send(400, util.format('[getAcsToken]: %j', e));
  });
}

function callCalculatorBridge(token, request, response) {
  if (request.body.hasOwnProperty('location') &&
    request.body.hasOwnProperty('operations') &&
    Array.isArray(request.body.operations)) {
    var operations = request.body.operations;
    var body =  '<?xml version="1.0" encoding="utf-8"?>' +
          '<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" ' +
          'xmlns:s="http://www.w3.org/2003/05/soap-envelope">' +
          '<s:Header>' +
          '<a:Action s:mustUnderstand="1">*</a:Action>' +
          '</s:Header>' +
          '<s:Body>' +
          '<request xmlns="http://windowsazure.cat.microsoft.com/samples/mobileservices" ' +
          'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' +
          '<location>' + request.body.location + '</location>' +
          '<operations>';
    var items = new Array(operations.length);
    var index = 0;
    for (var i = 0; i < operations.length; i++) {
      if (operations[i].hasOwnProperty('op') &&
        operations[i].hasOwnProperty('op1') &&
        operations[i].hasOwnProperty('op2')) {
          body = body + 
               '<operation>' +
               '<op>' + operations[i].op + '</op>' +
               '<op1>' + operations[i].op1 + '</op1>' +
               '<op2>' + operations[i].op2 + '</op2>' +
               '</operation>';
          items[index] = operations[i];
          index++;
        }
    }
    body = body + '</operations></request></s:Body></s:Envelope>';

    var headers = {
      'Accept': 'application/soap+xml',
      'Content-Type': 'application/soap+xml',
      'Content-Length': body.length,
      'Authorization': token
    };

    var agentOptions = {
        secureOptions: require('constants').SSL_OP_NO_TLSv1_2,
        ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM',
        honorCipherOrder: true
    };

    var agent = new https.Agent(agentOptions);

    var options = {
      host: bizTalkServiceUrl,
      path: '/default/calculatorservice',
      headers: headers,
      method: 'POST',
      agent: agent
    };

    console.log('[callCalculatorBridge]: options: %j\n\rrequest body:\r\n%s', options, body);

    var req = https.request(options, function (res) {
      var xml = '';
      console.log('[callCalculatorBridge]: statusCode: %d\n\rheaders: %j', res.statusCode, res.headers);

      res.on('data', function (data) {
        var chunk = data.toString('utf8');
        xml += chunk;
      });

      res.on('end', function () {
        createJsonResponse(response, xml, items);
      });
    });
    req.write(body, 'utf8');
    req.end();

    req.on('error', function (e) {
      console.error('[callCalculatorBridge]: ', e);
      response.send(400, util.format('[callCalculatorBridge]: %j', e));
    });
  }
  else {
    var message = "[callCalculatorBridge]: The request body is not in " + 
            "JSON format or doesn't contain all the necessary fields. request.body=" + 
            JSON.stringify(request.body);
    console.error(message);
    response.send(400, message);
  }
}

function createJsonResponse(response, xml, items) {
  if (response.statusCode == 200) {
    
    // 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(xml, function (error, json) {
      if (error) {
        // Log the error
        console.log('[callCalculatorBridge]: An error occurred while parsing the response: ', ex);
        response.send(400, '[callCalculatorBridge]: An error occurred while parsing the response.');
      }
      else {
        try {
          // flatten json object
          var flat = flatten(json);
          console.log('[callCalculatorBridge]: flat json: ', JSON.stringify(json));
          if (flat) {
            var results = flat["s:Envelope"]["s:Body"]["ns1:response"]["ns1:results"]["ns1:value"];
            if (results && !Array.isArray(results)) {
              results = [results];
            }
            for (var j = 0; j < results.length; j++) {
              items[j].result = results[j];
            }                  
          }
          response.send(200, items);
        }
        catch (ex) {
          // Log the error
          console.log('[callCalculatorBridge]: An error occurred while processing the response: ', ex);
          response.send(400, '[callCalculatorBridge]: An error occurred while processing the response.');
        }
      }
    });
  }
  else {
    response.send(400, '[callCalculatorBridge]: An error occurred while invoking the downstream service.');
  }
}

function flatten(obj, lowerCase) {
  if (Array.isArray(obj))  {
    if (obj.length == 1) {
      return flatten(obj[0]);
    }
    else {
      return obj.map(flatten); 
    }
  }
  if (typeof obj === 'string') {
    return obj;
  }
  if (obj === null || 
    obj === undefined ||
    Object.getOwnPropertyNames(obj).length === 0) {
    return ""; 
  }

  var flat = {};
  for (var k in obj) {
    if (obj.hasOwnProperty(k)) {
      var value = obj[k];
      if (null === value || undefined === value) {
        continue;
      }
      if (k !== '$') {
        flat[k] = flatten(value);
      }
    }
  }
  return flat;
}

var bizTalkServiceNamespace = '[YOUR-BIZTALK-SERVICE-NAMESPACE]';  
var bizTalkServiceUrl = bizTalkServiceNamespace + '.biztalk.windows.net';  
var bridgePath = '/default/CalculatorService';  
var bizTalkServiceScope = 'http://' + bizTalkServiceUrl + bridgePath;  
var acsNamespace = '[YOUR-BIZTALK-SERVICE-ACS-NAMESPACE]';  
var issuerName = '[YOUR-BIZTALK-SERVICE-ACS-ISSUER-NAME]';  
var issuerSecret = '[YOUR-BIZTALK-SERVICE-ACS-ISSUER-SECRET]';  
var https = require('https');  
var qs = require('querystring');  
var util = require('util');  
 
exports.post = function(request, response) { 
  https.globalAgent.options.secureProtocol = 'SSLv3_method'; 
  https.globalAgent.options.rejectUnauthorized = false; 
 
  getAcsToken(response, 
        function(token) { 
      callCalculatorBridge(token, request, response); 
  }); 
}; 
 
function getAcsToken(response, 
           callback) { 
  var options = { 
    host: acsNamespace + '.accesscontrol.windows.net', 
    path: '/WRAPv0.9/', 
    method: 'POST' 
  }; 
 
  var values = { 
    wrap_name: issuerName, 
    wrap_password: issuerSecret, 
    wrap_scope: bizTalkServiceScope 
  }; 
 
  console.log('[getAcsToken]: options: %j\n\rrequest body: %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 body = qs.parse(data.toString('utf8')); 
      if (res.statusCode == 200) { 
        if (body.hasOwnProperty('wrap_access_token')) { 
          var header = 'WRAP access_token=\"' + body.wrap_access_token + '\"'; 
          console.log('[getAcsToken]: header = %s', header); 
          callback(header); 
        } 
        else { 
          response.send(400, util.format("[getAcsToken]: ACS didn't return a valid token")); 
        } 
      } 
      else { 
        response.send(res.statusCode, util.format('[getAcsToken]: %s', data)); 
      } 
    }); 
  }); 
  req.write(qs.stringify(values)); 
  req.end(); 
 
  req.on('error', function (e) { 
    console.error('[getAcsToken]: ', e); 
    response.send(400, util.format('[getAcsToken]: %j', e)); 
  }); 
} 
 
function callCalculatorBridge(token, request, response) { 
  if (request.body.hasOwnProperty('location') && 
    request.body.hasOwnProperty('operations') && 
    Array.isArray(request.body.operations)) { 
    var operations = request.body.operations; 
    var body =  '<?xml version="1.0" encoding="utf-8"?>' + 
          '<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" ' + 
          'xmlns:s="http://www.w3.org/2003/05/soap-envelope">' + 
          '<s:Header>' + 
          '<a:Action s:mustUnderstand="1">*</a:Action>' + 
          '</s:Header>' + 
          '<s:Body>' + 
          '<request xmlns="http://windowsazure.cat.microsoft.com/samples/mobileservices" ' + 
          'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + 
          '<location>' + request.body.location + '</location>' + 
          '<operations>'; 
    var items = new Array(operations.length); 
    var index = 0; 
    for (var i = 0; i < operations.length; i++) { 
      if (operations[i].hasOwnProperty('op') && 
        operations[i].hasOwnProperty('op1') && 
        operations[i].hasOwnProperty('op2')) { 
          body = body +  
               '<operation>' + 
               '<op>' + operations[i].op + '</op>' + 
               '<op1>' + operations[i].op1 + '</op1>' + 
               '<op2>' + operations[i].op2 + '</op2>' + 
               '</operation>'; 
          items[index] = operations[i]; 
          index++; 
        } 
    } 
    body = body + '</operations></request></s:Body></s:Envelope>'; 
 
    var headers = { 
      'Accept': 'application/soap+xml', 
      'Content-Type': 'application/soap+xml', 
      'Content-Length': body.length, 
      'Authorization': token 
    }; 
 
    var agentOptions = { 
        secureOptions: require('constants').SSL_OP_NO_TLSv1_2, 
        ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', 
        honorCipherOrder: true 
    }; 
 
    var agent = new https.Agent(agentOptions); 
 
    var options = { 
      host: bizTalkServiceUrl, 
      path: '/default/calculatorservice', 
      headers: headers, 
      method: 'POST', 
      agent: agent 
    }; 
 
    console.log('[callCalculatorBridge]: options: %j\n\rrequest body:\r\n%s', options, body); 
 
    var req = https.request(options, function (res) { 
      var xml = ''; 
      console.log('[callCalculatorBridge]: statusCode: %d\n\rheaders: %j', res.statusCode, res.headers); 
 
      res.on('data', function (data) { 
        var chunk = data.toString('utf8'); 
        xml += chunk; 
      }); 
 
      res.on('end', function () { 
        createJsonResponse(response, xml, items); 
      }); 
    }); 
    req.write(body, 'utf8'); 
    req.end(); 
 
    req.on('error', function (e) { 
      console.error('[callCalculatorBridge]: ', e); 
      response.send(400, util.format('[callCalculatorBridge]: %j', e)); 
    }); 
  } 
  else { 
    var message = "[callCalculatorBridge]: The request body is not in " +  
            "JSON format or doesn't contain all the necessary fields. request.body=" +  
            JSON.stringify(request.body); 
    console.error(message); 
    response.send(400, message); 
  } 
} 
 
function createJsonResponse(response, xml, items) { 
  if (response.statusCode == 200) { 
     
    // 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(xml, function (error, json) { 
      if (error) { 
        // Log the error 
        console.log('[callCalculatorBridge]: An error occurred while parsing the response: ', ex); 
        response.send(400, '[callCalculatorBridge]: An error occurred while parsing the response.'); 
      } 
      else { 
        try { 
          // flatten json object 
          var flat = flatten(json); 
          console.log('[callCalculatorBridge]: flat json: ', JSON.stringify(json)); 
          if (flat) { 
            var results = flat["s:Envelope"]["s:Body"]["ns1:response"]["ns1:results"]["ns1:value"]; 
            if (results && !Array.isArray(results)) { 
              results = [results]; 
            } 
            for (var j = 0; j < results.length; j++) { 
              items[j].result = results[j]; 
            }                   
          } 
          response.send(200, items); 
        } 
        catch (ex) { 
          // Log the error 
          console.log('[callCalculatorBridge]: An error occurred while processing the response: ', ex); 
          response.send(400, '[callCalculatorBridge]: An error occurred while processing the response.'); 
        } 
      } 
    }); 
  } 
  else { 
    response.send(400, '[callCalculatorBridge]: An error occurred while invoking the downstream service.'); 
  } 
} 
 
function flatten(obj, lowerCase) { 
  if (Array.isArray(obj))  { 
    if (obj.length == 1) { 
      return flatten(obj[0]); 
    } 
    else { 
      return obj.map(flatten);  
    } 
  } 
  if (typeof obj === 'string') { 
    return obj; 
  } 
  if (obj === null ||  
    obj === undefined || 
    Object.getOwnPropertyNames(obj).length === 0) { 
    return "";  
  } 
 
  var flat = {}; 
  for (var k in obj) { 
    if (obj.hasOwnProperty(k)) { 
      var value = obj[k]; 
      if (null === value || undefined === value) { 
        continue; 
      } 
      if (k !== '$') { 
        flat[k] = flatten(value); 
      } 
    } 
  } 
  return flat; 
}
 
 NOTE: make sure to replace the following placeholders in the upper part of the code before saving the script:
  • [YOUR-BIZTALK-SERVICE-NAMESPACE]: specify the name of your BizTalk Service namespace.
  • [YOUR-BIZTALK-SERVICE-ACS-NAMESPACE]: specify the name of the ACS namespace thet issues the security tokens to authenticate against your BizTalk Service
  • [YOUR-BIZTALK-SERVICE-ACS-ISSUER-NAME]: specify the name of a service identity used to access your BizTalk Service (e.g. owner)
  • [YOUR-BIZTALK-SERVICE-ACS-ISSUER-SECRET]: specify the password for the selected service identity.

For more information, see BizTalk Services: Provisioning Using Windows Azure Management Portal.

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://servicebus.scm.azure-mobile.net/servicebus.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:
  • CalculatorContracts: contains the service and data contracts used by the WCF service.
  • CalculatorService: contains the WCF service that represents the line of business application invoked by the mobile service via a BizTalk Service. The service runs within a console application, but the project can be easily changed to host the service in IIS.
  • CalculatorClient: contains a console application that you can use to test the 3 endpoints exposed by the WCF service on the Windows Azure Service Bus.
  • CalculatorBridge: contains the artifacts used by the BizTalk Service:
    • XML Request-Reply Bridge
    • XML Schemas
    • Maps
  • BridgeClient: contains a console application that you can use to send request messages to the XML Request-Reply Bridge exposed by the BizTalk Service.
  • HTML5: contains the HTML5/JavaScript client for 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.

 

CalculatorContracts

The following table contains the code of the ICalculator the service contract implemented by the CalculatorService.

#region Using Directives
using System.ServiceModel;
using System.ServiceModel.Web;
#endregion

namespace CalculatorContracts
{
    [ServiceContract(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
    public interface ICalculator
    {
        [WebInvoke(Method = "POST", UriTemplate = "processrequest", 
                   RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml)]
        [OperationContract(Action = "ProcessRequest", ReplyAction = "ProcessRequestResponse")]
        ResponseMessage ProcessRequest(RequestMessage request);
    }
}

#region Using Directives 
using System.ServiceModel; 
using System.ServiceModel.Web; 
#endregion 
 
namespace CalculatorContracts 
{ 
    [ServiceContract(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")] 
    public interface ICalculator 
    { 
        [WebInvoke(Method = "POST", UriTemplate = "processrequest",  
                   RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml)] 
        [OperationContract(Action = "ProcessRequest", ReplyAction = "ProcessRequestResponse")] 
        ResponseMessage ProcessRequest(RequestMessage request); 
    } 
}
Please refer to the Visual Studio solution for the code of the request and response messages here ommited for brevity.

 

CalculatorService

The service project contains the code of the CalculatorService invoked by the mobile service.
The following table contains the code of the CalculatorService class.
#region Using Directives
using System;
using System.ServiceModel;
using CalculatorContracts;
#endregion

namespace CalculatorService
{
  [ServiceBehavior(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
  public class CalculatorService : ICalculator
  {
    #region Private Static Fields
    private static readonly string line = new string('-', 99);
    #endregion

    #region Public Methods
    public ResponseMessage ProcessRequest(RequestMessage request)
    {
      var responseMessage = new ResponseMessage();
      if (request != null &&
        request.CalculatorRequest != null &&
        request.CalculatorRequest.Operations != null)
      {
        foreach (var operation in request.CalculatorRequest.Operations)
        {
          switch (operation.Operator)
          {
            case "+":
              responseMessage.CalculatorResponse.Results.Add((Add(operation.Operand1, operation.Operand2)));
              break;
            case "-":
              responseMessage.CalculatorResponse.Results.Add(Subtract(operation.Operand1, operation.Operand2));
              break;
            case "*":
              responseMessage.CalculatorResponse.Results.Add(Multiply(operation.Operand1, operation.Operand2));
              break;
            case "/":
              responseMessage.CalculatorResponse.Results.Add(Divide(operation.Operand1, operation.Operand2));
              break;
          }
          Console.WriteLine(line);
        }
      }
      return responseMessage;
    }
    #endregion

    #region Private Methods
    private static double Add(double n1, double n2)
    {
      var n = n1 + n2;
      Console.ForegroundColor = ConsoleColor.Cyan;
      Console.WriteLine("{0} + {1} = {2}", n1, n2, n);
      Console.ResetColor();
      return n;
    }

    private static double Subtract(double n1, double n2)
    {
      var n = n1 - n2;
      Console.ForegroundColor = ConsoleColor.Green;
      Console.WriteLine("{0} - {1} = {2}", n1, n2, n);
      Console.ResetColor();
      return n;
    }

    private static double Multiply(double n1, double n2)
    {
      var n = n1 * n2;
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine("{0} * {1} = {2}", n1, n2, n);
      Console.ResetColor();
      return n;
    }

    private static double Divide(double n1, double n2)
    {
      var n = n1 / n2;
      Console.ForegroundColor = ConsoleColor.Yellow;
      Console.WriteLine("{0} / {1} = {2}", n1, n2, n);
      Console.ResetColor();
      return n;
    }
    #endregion
  }
}
As you can easily notice in the figure below, the service exposes three different endpoints that in turn use three different bindings:
The Two-Way Relay Endpoints are configured to invoke only the NetTcpRelayBinding, but the MessageFlowItinierary of the CalculatorBridge project can be easily changed to call any of the endpoints exposed by the CalculatorService. The following table contains the configuration file of the service.
<?xml version="1.0"?>
<configuration>
  <system.diagnostics>
  <sources>
    <source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
    <listeners>
      <add type="System.Diagnostics.DefaultTraceListener" name="Default">
      <filter type=""/>
      </add>
      <add name="ServiceModelMessageLoggingListener">
      <filter type=""/>
      </add>
    </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add initializeData="C:\CalculatorService.svclog" 
       type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, 
             PublicKeyToken=b77a5c561934e089" 
       name="ServiceModelMessageLoggingListener" 
       traceOutputOptions="Timestamp">
    <filter type=""/>
    </add>
  </sharedListeners>
  </system.diagnostics>

  <system.serviceModel>

  <!-- Diagnostics -->
  <diagnostics performanceCounters="All">
    <messageLogging logEntireMessage="true" 
            logMalformedMessages="true" 
            logMessagesAtServiceLevel="true" 
            logMessagesAtTransportLevel="true"/>
  </diagnostics>
  <!-- Binding Configuration -->
  <bindings>
    <netTcpRelayBinding>
    <binding name="netTcpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </netTcpRelayBinding>
    <basicHttpRelayBinding>
    <binding name="basicHttpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </basicHttpRelayBinding>
    <webHttpRelayBinding>
    <binding name="webHttpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </webHttpRelayBinding>
  </bindings>
  <!-- Behavior Configuration -->
  <behaviors>
    <endpointBehaviors>
    <behavior name="serviceBusEndpointBehavior">
      <transportClientEndpointBehavior>
      <tokenProvider>
        <sharedSecret issuerName="owner" issuerSecret="[YOUR-ISSUER-SECRET]"/>
      </tokenProvider>
      </transportClientEndpointBehavior>
      <serviceRegistrySettings discoveryMode="Public" displayName="config"/>
    </behavior>
    <behavior name="webHttpEndpointBehavior">
      <webHttp defaultBodyStyle="Bare" />
      <transportClientEndpointBehavior>
      <tokenProvider>
        <sharedSecret issuerName="owner" issuerSecret="[YOUR-ISSUER-SECRET]"/>
      </tokenProvider>
      </transportClientEndpointBehavior>
      <serviceRegistrySettings discoveryMode="Public" displayName="config"/>
    </behavior>
    </endpointBehaviors>
  </behaviors>
  <!-- Service Conbfiguration -->
  <services>
    <service name="CalculatorService.CalculatorService">
    <endpoint address="sb://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/calculator/nettcp" 
          behaviorConfiguration="serviceBusEndpointBehavior" 
          binding="netTcpRelayBinding" 
          bindingConfiguration="netTcpRelayBinding" 
          contract="CalculatorContracts.ICalculator" 
          name="netTcpRelayBinding"/>
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/calculator/basichttp"
           behaviorConfiguration="serviceBusEndpointBehavior"
           binding="basicHttpRelayBinding"
           bindingConfiguration="basicHttpRelayBinding"
           contract="CalculatorContracts.ICalculator"
           name="basicHttpRelayBinding"/>
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/calculator/webhttp"
          behaviorConfiguration="webHttpEndpointBehavior"
          binding="webHttpRelayBinding"
          bindingConfiguration="webHttpRelayBinding"
          contract="CalculatorContracts.ICalculator"
          name="webHttpRelayBinding"/>
    </service>
  </services>
  <extensions>
    <behaviorExtensions>
    <add name="connectionStatusBehavior" 
         type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="transportClientEndpointBehavior" 
         type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="serviceRegistrySettings" 
         type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

    <add name="tokenProviderEndpointBehavior" 
         type="Microsoft.WindowsAzure.CAT.ServiceBusForWindowsServer.Behaviors.TokenProviderBehaviorExtensionElement, 
         Microsoft.WindowsAzure.CAT.ServiceBusForWindowsServer.Behaviors, Version=1.0.0.0, Culture=neutral, 
         PublicKeyToken=197ec3eb961f773c"/>
    </behaviorExtensions>
    <bindingElementExtensions>
    <add name="netMessagingTransport" 
         type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="tcpRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="httpRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, 
         Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="httpsRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, 
         Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="onewayRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </bindingElementExtensions>
    <bindingExtensions>
    <add name="basicHttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="webHttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="ws2007HttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netTcpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netOnewayRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netEventRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netMessagingBinding" 
         type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </bindingExtensions>
  </extensions>
  </system.serviceModel>
  <startup>
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
  </startup>
</configuration>
NOTE: make sure to replace the following placeholders in the app.config file in the project and in the CalculatorServiceEurope.exe.config and CalculatorServiceUS.exe.config files that you can find in the CalculatorService/bin/Debug folder:
  • [YOUR-SERVICE-BUS-NAMESPACE]: specify the name of your Service Bus namespace.
  • [YOUR-ISSUER-SECRET]: specify the issuer secret of the owner service identity for your Service Bus namespace.

CalculatorClient

This project contains a console application that you can use to test the three endpoints exposed by the CalculatorService.
The following table contains the code of the Program.cs class.
#region Using Directives
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using CalculatorContracts;
#endregion

namespace CalculatorClient
{
  internal class Program
  {
    static readonly string line = new string('-', 104);

    private static void Main()
    {
      try
      {
        string endpoint;
        while ((endpoint = SelectEndpoint()) != null)
        {
          try
          {
            // Print status
            Console.WriteLine(line);
            Console.Write("Calling the service: ");
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.Write("Endpoint");
            Console.ResetColor();
            Console.WriteLine("={0} ", endpoint);
            Console.WriteLine(line);

            // Create channel factory
            var factory = new ChannelFactory<ICalculator>(endpoint);

            // Create channel
            var channel = factory.CreateChannel();

            // Create request message
            var calculatorRequest = new CalculatorRequest();
            calculatorRequest.Operations.Add(new Operation { Operand1 = 1, Operand2 = 2, Operator = "+" });
            calculatorRequest.Operations.Add(new Operation { Operand1 = 8, Operand2 = 2, Operator = "-" });

            // Call calculator service
            var requestMessage = new RequestMessage(calculatorRequest);
            var responseMessage = channel.ProcessRequest(requestMessage);

            // Print resultList
            if (responseMessage == null || responseMessage.CalculatorResponse == null)
            {
              continue;
            }
            PrintResults(calculatorRequest.Operations, responseMessage.CalculatorResponse.Results);
          }
          catch (Exception ex)
          {
            Console.WriteLine(ex.Message);
          }
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.WriteLine("Press [Enter] to exit");
        Console.ReadLine();
      }
    }

    private static string SelectEndpoint()
    {
      // Set Window Size
      Console.WindowWidth = 105;
      Console.BufferWidth = 105;
      Console.WindowHeight = 25;
      Console.BufferHeight = 200;

      // Get "system.serviceModel/client section
      Console.WriteLine("Select a client endpoint:");

      Console.WriteLine(line);
      var clientSection = ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;
      if (clientSection == null ||
        clientSection.Endpoints == null ||
        clientSection.Endpoints.Count == 0)
      {
        return null;
      }

      // Show client endpoints
      var endpointList = clientSection.Endpoints.Cast<ChannelEndpointElement>().Where(e => e.Name.Contains("Relay")).ToList();
      if (endpointList.Count == 0)
      {
        Console.WriteLine("There are no client endpoints defined in the configuration file.");
        Console.WriteLine(line);
        Console.WriteLine("Press [Enter] to exit");
        Console.ReadLine();
        return null;
      }
      for (var i = 0; i < endpointList.Count; i++)
      {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.Write("[{0}] ", i + 1);
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.Write("{0}: ", endpointList[i].Name);
        Console.ResetColor();
        Console.WriteLine(endpointList[i].Address);
      }
      // Add exit option
      Console.ForegroundColor = ConsoleColor.Green;
      Console.Write("[{0}] ", endpointList.Count + 1);
      Console.ForegroundColor = ConsoleColor.Yellow;
      Console.WriteLine("Exit");
      Console.ResetColor();
      Console.WriteLine(line);

      // Select an option
      Console.Write("Select a numeric key between [1] and [{0}]: ", endpointList.Count + 1);
      var key = Console.ReadKey().KeyChar;
      Console.WriteLine();
      return key - '1' == endpointList.Count ? null : endpointList[key - '1'].Name;
    }

    private static void PrintResults(IReadOnlyList<Operation> operationList, IReadOnlyList<double> resultList)
    {
      if (operationList == null ||
        resultList == null ||
        operationList.Count != resultList.Count)
      {
        return;
      }
      Console.ForegroundColor = ConsoleColor.Yellow;
      Console.WriteLine("Results:");
      Console.ResetColor();
      Console.WriteLine(line);
      for (var i = 0; i < resultList.Count; i++)
      {
        switch (operationList[i].Operator)
        {
          case "+":
            Console.ForegroundColor = ConsoleColor.Cyan;
            break;
          case "-":
            Console.ForegroundColor = ConsoleColor.Green;
            break;
          case "*":
            Console.ForegroundColor = ConsoleColor.Red;
            break;
          case "/":
          case @"\":
            Console.ForegroundColor = ConsoleColor.Yellow;
            break;
        }        
        Console.WriteLine("{0} {1} {2} = {3}", 
                  operationList[i].Operand1, 
                  operationList[i].Operator,
                  operationList[i].Operand2, 
                  resultList[i]);
        Console.ResetColor();
        Console.WriteLine(line);
      }
    }
  }
}
The following table contains the configuration file of the client application.
<?xml version="1.0"?>
<configuration>
  <system.diagnostics>
  <sources>
    <source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
    <listeners>
      <add type="System.Diagnostics.DefaultTraceListener" name="Default">
      <filter type=""/>
      </add>
      <add name="ServiceModelMessageLoggingListener">
      <filter type=""/>
      </add>
    </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add initializeData="C:\CalculatorClient.svclog" 
       type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, 
             Culture=neutral, PublicKeyToken=b77a5c561934e089" 
       name="ServiceModelMessageLoggingListener" 
       traceOutputOptions="Timestamp">
    <filter type=""/>
    </add>
  </sharedListeners>
  </system.diagnostics>

  <system.serviceModel>

  <!-- Diagnostics -->
  <diagnostics performanceCounters="All">
    <messageLogging logEntireMessage="true" 
            logMalformedMessages="true" 
            logMessagesAtServiceLevel="true" 
            logMessagesAtTransportLevel="true"/>
  </diagnostics>
  <!-- Binding Configuration -->
  <bindings>
    <netTcpRelayBinding>
    <binding name="netTcpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </netTcpRelayBinding>
    <basicHttpRelayBinding>
    <binding name="basicHttpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </basicHttpRelayBinding>
    <webHttpRelayBinding>
    <binding name="webHttpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </webHttpRelayBinding>
  </bindings>
  <!-- Behavior Configuration -->
  <behaviors>
    <endpointBehaviors>
    <behavior name="serviceBusEndpointBehavior">
      <transportClientEndpointBehavior>
      <tokenProvider>
        <sharedSecret issuerName="owner" issuerSecret="[YOUR-ISSUER-SECRET]"/>
      </tokenProvider>
      </transportClientEndpointBehavior>
    </behavior>
    <behavior name="webHttpEndpointBehavior">
      <webHttp defaultBodyStyle="Bare" />
      <transportClientEndpointBehavior>
      <tokenProvider>
        <sharedSecret issuerName="owner" issuerSecret="[YOUR-ISSUER-SECRET]"/>
      </tokenProvider>
      </transportClientEndpointBehavior>
    </behavior>
    </endpointBehaviors>
  </behaviors>
  <!-- Service Conbfiguration -->
  <client>
    <endpoint address="sb://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/calculator/nettcp"
        behaviorConfiguration="serviceBusEndpointBehavior"
        binding="netTcpRelayBinding"
        bindingConfiguration="netTcpRelayBinding"
        contract="CalculatorContracts.ICalculator"
        name="netTcpRelayBinding"/>
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/calculator/basichttp"
          behaviorConfiguration="serviceBusEndpointBehavior"
          binding="basicHttpRelayBinding"
          bindingConfiguration="basicHttpRelayBinding"
          contract="CalculatorContracts.ICalculator"
          name="basicHttpRelayBinding"/>
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/calculator/webhttp"
        behaviorConfiguration="webHttpEndpointBehavior"
        binding="webHttpRelayBinding"
        bindingConfiguration="webHttpRelayBinding"
        contract="CalculatorContracts.ICalculator"
        name="webHttpRelayBinding"/>
  </client>
  <extensions>
    <behaviorExtensions>
    <add name="connectionStatusBehavior" 
         type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="transportClientEndpointBehavior" 
         type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="serviceRegistrySettings" 
         type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="tokenProviderEndpointBehavior" 
         type="Microsoft.WindowsAzure.CAT.ServiceBusForWindowsServer.Behaviors.TokenProviderBehaviorExtensionElement, 
         Microsoft.WindowsAzure.CAT.ServiceBusForWindowsServer.Behaviors, Version=1.0.0.0, Culture=neutral, 
         PublicKeyToken=197ec3eb961f773c"/>
    </behaviorExtensions>
    <bindingElementExtensions>
    <add name="netMessagingTransport" 
         type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="tcpRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="httpRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, 
         Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="httpsRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, 
         Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="onewayRelayTransport" 
         type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </bindingElementExtensions>
    <bindingExtensions>
    <add name="basicHttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="webHttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="ws2007HttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netTcpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netOnewayRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netEventRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="netMessagingBinding" 
     type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, 
         Microsoft.ServiceBus, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </bindingExtensions>
  </extensions>
  </system.serviceModel>
  <startup>
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
  </startup>
</configuration>
NOTE: make sure to replace the following placeholders in the app.config file:
  • [YOUR-SERVICE-BUS-NAMESPACE]: specify the name of your Service Bus namespace.
  • [YOUR-ISSUER-SECRET]: specify the issuer secret of the owner service identity for your Service Bus namespace.

CalculatorBridge

This project contains the artifacts used by the BizTalk Service:

  • XML Schemas:
    • MobileServiceRequest: defines the format of request messages sent by the mobile service to the bridge.
    • MobileServiceResponse: defines the format of response messages sent by the bridge to the mobile service.
    • CalculatorRequest: defines the format of request messages sent by the bridge to the calculator service.
    • CalculatorResponse: defines the format of response messages sent by the calculator service to the bridge.
  • Maps:
    • MobileServiceRequestMap: transforms a MobileServiceRequest message into a CalculatorRequest message.
    • MobileServiceResponseMap: transforms a CalculatorResponse message into a MobileServiceResponse message.
  • Message Flow Itineraries:
    • MessageFlowItinerary: defines the XML Request-Reply Bridge used to route messages to the EUROPE and US calculator services.

The following figure shows the structure of the MessageFlowItinerary:

The following picture shows the Route Ordering Table of the itinerary. As you can see, the Location promoted property is used to decide which of the EUROPE or US calculator service is going to receive the request: if Location = 'EUROPE', the request is sent to the EUROPE calculator service via the corresponding Two-Way Relay Endpoint, otherwise the request is sent to the US calculator service.

THe following picture shows the Route Actions table for both the Two-Way Relay Endpoints. As you can see, the Route action sets two headers:

  • SOAP Action header used by the service to dispatch the request to the right method.
  • A custom header called Location.

The following picture shows the Xml Request-Reply Bridge:

The following table contains the content of the Europe.config configuration file for the EUROPE Two-Way Relay Endpoint.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
  <behaviors>
    <endpointBehaviors>
    <behavior name="serviceBusEndpointBehavior">
      <transportClientEndpointBehavior>
      <tokenProvider>
        <sharedSecret issuerName="owner" issuerSecret="[YOUR-ISSUER-SECRET]" />
      </tokenProvider>
      </transportClientEndpointBehavior>
    </behavior>
    <behavior name="webHttpEndpointBehavior">
      <webHttp defaultBodyStyle="Bare" />
      <transportClientEndpointBehavior>
      <tokenProvider>
        <sharedSecret issuerName="owner" issuerSecret="[YOUR-ISSUER-SECRET]"/>
      </tokenProvider>
      </transportClientEndpointBehavior>
    </behavior>
    </endpointBehaviors>
  </behaviors>
  <extensions>
    <behaviorExtensions>
    <add name="transportClientEndpointBehavior" 
         type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, 
         Microsoft.ServiceBus, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </behaviorExtensions>
    <bindingElementExtensions>
    <add name="netMessagingTransport" 
         type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, 
         Microsoft.ServiceBus, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </bindingElementExtensions>
    <bindingExtensions>
    <add name="basicHttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <add name="webHttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <add name="ws2007HttpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <add name="netTcpRelayBinding" 
         type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, 
         Microsoft.ServiceBus, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <add name="netMessagingBinding" 
         type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, 
         Microsoft.ServiceBus, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </bindingExtensions>
  </extensions>
  <bindings>
    <netTcpRelayBinding>
    <binding name="netTcpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </netTcpRelayBinding>
    <basicHttpRelayBinding>
    <binding name="basicHttpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </basicHttpRelayBinding>
    <webHttpRelayBinding>
    <binding name="webHttpRelayBinding">
      <security mode="Transport" relayClientAuthenticationType="RelayAccessToken"/>
    </binding>
    </webHttpRelayBinding>
  </bindings>
  <client>
    <clear />
    <endpoint address="sb://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/europe/calculator/nettcp"
        behaviorConfiguration="serviceBusEndpointBehavior"
        binding="netTcpRelayBinding"
        bindingConfiguration="netTcpRelayBinding"
        contract="System.ServiceModel.Routing.IRequestReplyRouter"
        name="netTcpRelayBinding"/>
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/europe/calculator/basichttp/"
        behaviorConfiguration="serviceBusEndpointBehavior" 
        binding="basicHttpRelayBinding"
        bindingConfiguration="basicHttpRelayBinding" 
        contract="System.ServiceModel.Routing.IRequestReplyRouter"
        name="basicHttpRelayBinding" />
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/europe/calculator/webhttp"
        behaviorConfiguration="webHttpEndpointBehavior"
        binding="webHttpRelayBinding"
        bindingConfiguration="webHttpRelayBinding"
        contract="System.ServiceModel.Routing.IRequestReplyRouter"
        name="webHttpRelayBinding"/>
  </client>
  </system.serviceModel>
</configuration>

NOTE: make sure to replace the following placeholders in the Europe.config and US.config files:

  • [YOUR-SERVICE-BUS-NAMESPACE]: specify the name of your Service Bus namespace.
  • [YOUR-ISSUER-SECRET]: specify the issuer secret of the owner service identity for your Service Bus namespace.

    BridgeClient

    This project contains a console application that you can use to test the XML Request-Reply Bridge.
    The following table contains the code of the Program.cs class.
     
    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Runtime.Serialization;
    using System.ServiceModel.Channels;
    using System.Text;
    using System.Web;
    using System.Xml;
    using System.Xml.Linq;
    #endregion
    
    namespace BridgeClient
    {
      internal class Program
      {
        private static readonly string line = new string('-', 104);
        private const string RuntimeAddress = 
            "https://[YOUR-BIZTALK-SERVICE-NAMESPACE].biztalk.windows.net/default/calculatorservice";
        private const string IssuerName = "[YOUR-BIZTALK-SERVICE-ACS-ISSUER-NAME]";
        private const string IssuerSecret = "[YOUR-BIZTALK-SERVICE-ACS-ISSUER-SECRET]";
        private const string AcsNamespace = "[YOUR-BIZTALK-SERVICE-ACS-NAMESPACE]";
    
        private static void Main()
        {
          try
          {
            string location;
            while ((location = SelectAction()) != null)
            {
              try
              {
                // Print status
                Console.WriteLine(line);
                Console.Write("Calling the service: ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("Endpoint");
                Console.ResetColor();
                Console.WriteLine("={0} ", RuntimeAddress);
                Console.WriteLine(line);
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("Location");
                Console.ResetColor();
                Console.WriteLine("={0} ", location);
                Console.WriteLine(line);
    
                // Create request message
                var calculatorRequest = new CalculatorRequest(location);
                calculatorRequest.Operations.Add(new Operation { Operand1 = 35, Operand2 = 54, Operator = "+" });
                calculatorRequest.Operations.Add(new Operation { Operand1 = 97, Operand2 = 51, Operator = "-" });
                calculatorRequest.Operations.Add(new Operation { Operand1 = 13, Operand2 = 7, Operator = "*" });
                calculatorRequest.Operations.Add(new Operation { Operand1 = 88, Operand2 = 8, Operator = "/" });
    
                var requestSerializer = new DataContractSerializer(typeof(CalculatorRequest));
                string requestMessage;
                using (var memoryStream = new MemoryStream())
                {
                  requestSerializer.WriteObject(memoryStream, calculatorRequest);
                  memoryStream.Position = 0;
                  using (var bodyReader = XmlReader.Create(memoryStream))
                  {
                    var message = Message.CreateMessage(MessageVersion.Default, "*", bodyReader);
                    using (var messageStream = new MemoryStream())
                    {
                      using (var xmlWriter = XmlWriter.Create(messageStream, 
                                                              new XmlWriterSettings { Encoding = Encoding.UTF8 }))
                      {
                        message.WriteMessage(xmlWriter);
                        xmlWriter.Flush();
                      }
                      messageStream.Position = 0;
                      using (var reader = new StreamReader(messageStream))
                      {
                        requestMessage = reader.ReadToEnd();
                      }
                    }
                  }
                }
                var endpointToGetAcsTokenForBuilder = new UriBuilder(RuntimeAddress)
                  {
                    Scheme = Uri.UriSchemeHttp,
                    Port = -1
                  };
                var runtimeToken = GetAccessControlToken(endpointToGetAcsTokenForBuilder.ToString(), 
                                                         IssuerName, IssuerSecret, AcsNamespace);
    
                var httpClient = new HttpClient
                {
                  BaseAddress = new Uri(RuntimeAddress)
                };
    
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/soap+xml"));
                var authorizationHeader = "access_token=\"" + HttpUtility.UrlDecode(runtimeToken) + "\"";
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("WRAP", authorizationHeader);
    
                if (string.IsNullOrEmpty(requestMessage))
                {
                  continue;
                }
                var response = httpClient.PostAsync(string.Empty, 
                                                    new StringContent(requestMessage, 
                                                                      Encoding.UTF8, 
                                                                      "application/soap+xml")).Result;
                if (response.IsSuccessStatusCode)
                {
                  XNamespace ns = "http://windowsazure.cat.microsoft.com/samples/mobileservices";
                  var document = XDocument.Load(response.Content.ReadAsStreamAsync().Result);
                  var responseNode = document.Descendants(ns + "response").First();
                  if (responseNode != null)
                  {
                    using (var reader = responseNode.CreateReader())
                    {
                      // Deserialize response message
                      var serializer = new DataContractSerializer(typeof(CalculatorResponse));
                      var calculatorResponse = serializer.ReadObject(reader, false) as CalculatorResponse;
                        
                      //Print results
                      if (calculatorResponse == null || calculatorResponse.Results == null)
                      {
                        continue;
                      }
                      PrintResults(calculatorRequest.Operations, calculatorResponse.Results);
                    }
                  }
                }
                else
                {
                  var responseContent = response.Content.ReadAsStringAsync().Result;
                  PrintError(response.StatusCode, 
                             string.IsNullOrEmpty(responseContent) ? "NULL" : responseContent);
                }
              }
              catch (Exception ex)
              {
                Console.WriteLine(ex.Message);
              }
            }
          }
          catch (Exception ex)
          {
            Console.WriteLine(ex.Message);
            Console.WriteLine("Press [Enter] to exit");
            Console.ReadLine();
          }
        }
    
        private static string SelectAction()
        {
          // Set Window Size
          Console.WindowWidth = 105;
          Console.BufferWidth = 105;
          Console.WindowHeight = 25;
          Console.BufferHeight = 200;
    
          // Print Select action
          Console.WriteLine("Select action:");
          Console.WriteLine(line);
    
          var list = new List<string> {"EUROPE", "US"};
          // Print send request to Europe Service
          Console.ForegroundColor = ConsoleColor.Green;
          Console.Write("[1]");
          Console.ResetColor();
          Console.Write(" Send request to ");
          Console.ForegroundColor = ConsoleColor.Yellow;
          Console.Write("EUROPE");
          Console.ResetColor();
          Console.WriteLine(" calculator service.");
    
          // Print send request to Europe Service
          Console.ForegroundColor = ConsoleColor.Green;
          Console.Write("[2]");
          Console.ResetColor();
          Console.Write(" Send request to ");
          Console.ForegroundColor = ConsoleColor.Yellow;
          Console.Write("US");
          Console.ResetColor();
          Console.WriteLine(" calculator service.");
    
          // Add exit option
          Console.ForegroundColor = ConsoleColor.Green;
          Console.Write("[3]");
          Console.ResetColor();
          Console.WriteLine(" Exit");
          Console.WriteLine(line);
    
          // Select an option
          Console.Write("Select a numeric key between [1] and [3]: ");
          var key = 'a';
          while (key < '1' || key > '9')
          {
            key = Console.ReadKey(true).KeyChar;
          }
          Console.WriteLine();
          return key - '1' == list.Count ? null : list[key - '1'];
        }
    
        /// <summary>
        /// Get the Access Control token for the Service Bus URI
        /// </summary>
        /// <param name="endpointUri">Represents the End Point URI</param>
        /// <param name="issuerName">Issuer name for the Service Bus URI</param>
        /// <param name="issuerKey">Issuer key for the Service Bus URI</param>
        /// <param name="acsNamespace">ACS namespace.</param>
        /// <returns>Access Control token</returns>
        private static string GetAccessControlToken(string endpointUri, 
                                                    string issuerName, 
                                                    string issuerKey, 
                                                    string acsNamespace)
        {
          var acsAddress = new UriBuilder(Uri.UriSchemeHttps + "://" + acsNamespace + ".accesscontrol.windows.net").ToString();
          return GetAcsToken(acsAddress, issuerName, issuerKey, endpointUri);
        }
    
        /// <summary>
        /// Gets the ACS token for the specified Service Bus URI
        /// </summary>
        /// <param name="acsAddress">Represents ACS address</param>
        /// <param name="issuerName">Issuer name for the specified Service Bus namespace</param>
        /// <param name="issuerKey">Issuer key for the specified Service Bus namespace</param>
        /// <param name="appliesToAddress">Represents Service Bus URI</param>
        /// <returns>ACS Token</returns>
        private static string GetAcsToken(string acsAddress, 
                                          string issuerName, 
                                          string issuerKey, 
                                          string appliesToAddress)
        {
          using (var client = new WebClient())
          {
            client.BaseAddress = acsAddress;
    
            var values = new NameValueCollection
              {
                {"wrap_name", issuerName},
                {"wrap_password", issuerKey},
                {"wrap_scope", appliesToAddress}
              };
    
            var responseBytes = client.UploadValues("WRAPv0.9/", "POST", values);
    
            var response = Encoding.UTF8.GetString(responseBytes);
    
            // Extract the SWT token and return it.
            return response
              .Split('&')
              .Single(value => value.StartsWith("wrap_access_token=", 
                                                StringComparison.OrdinalIgnoreCase))
              .Split('=')[1];
          }
        }
    
        private static void PrintError(HttpStatusCode statusCode, string error)
        {
          if (string.IsNullOrEmpty(error))
          {
            return;
          }
          Console.ForegroundColor = ConsoleColor.Yellow;
          Console.Write("StatusCode: ");
          Console.ResetColor();
          Console.Write(statusCode);
          Console.ForegroundColor = ConsoleColor.Yellow;
          Console.Write(" Body: ");
          Console.ResetColor();
          Console.WriteLine(error);
          Console.WriteLine(line);
        }
    
        private static void PrintResults(IReadOnlyList<Operation> operationList, 
                                         IReadOnlyList<double> resultList)
        {
          if (operationList == null ||
            resultList == null ||
            operationList.Count != resultList.Count)
          {
            return;
          }
          Console.ForegroundColor = ConsoleColor.Yellow;
          Console.WriteLine("Results:");
          Console.ResetColor();
          Console.WriteLine(line);
          for (var i = 0; i < resultList.Count; i++)
          {
            switch (operationList[i].Operator)
            {
              case "+":
                Console.ForegroundColor = ConsoleColor.Cyan;
                break;
              case "-":
                Console.ForegroundColor = ConsoleColor.Green;
                break;
              case "*":
                Console.ForegroundColor = ConsoleColor.Red;
                break;
              case "/":
              case @"\":
                Console.ForegroundColor = ConsoleColor.Yellow;
                break;
            }
            Console.WriteLine("{0} {1} {2} = {3}",
                      operationList[i].Operand1,
                      operationList[i].Operator,
                      operationList[i].Operand2,
                      resultList[i]);
            Console.ResetColor();
            Console.WriteLine(line);
          }
        }
      }
    }
     NOTE: make sure to replace the following placeholders in the Program.cs class:
    • [YOUR-BIZTALK-SERVICE-NAMESPACE]: specify the name of your BizTalk Service namespace.
    • [YOUR-BIZTALK-SERVICE-ACS-NAMESPACE]: specify the name of the ACS namespace thet issues the security tokens to authenticate against your BizTalk Service
    • [YOUR-BIZTALK-SERVICE-ACS-ISSUER-NAME]: specify the name of a service identity used to access your BizTalk Service (e.g. owner)
    • [YOUR-BIZTALK-SERVICE-ACS-ISSUER-SECRET]: specify the password for the selected service identity.

    HTML5/JavaScript Client

    The HTML5/JavaScript client application calls the mobile service useing the invokeApi method exposed by the MobileServiceClient contained in the MobileServices.Web-1.0.0.min.js script. This method can be used to invoke any HTTP method. For more information, see Custom API in Azure Mobile Services – Client SDKs by Carlos Figueira.

    The following table contains the code the page.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]'); // Read current data and rebuild UI. // If you plan to generate complex UIs like this, consider using a JavaScript templating library. function sendOperations() { $('#summary').html('Sending operations...'); var rows = $('#operations > tbody tr'); if (rows.length == 0) { $('#summary').html('The operation list does not contain any operation.'); return; } var operations = new Array(rows.length); for (var i = 0; i < rows.length; i++) { var index = i + 1; operations[i] = { 'op1': $('#operations > tbody tr:nth-child(' + index + ') td:nth-child(1) input').val(), 'op': $('#operations > tbody tr:nth-child(' + index + ') td:nth-child(2) select :selected').val(), 'op2': $('#operations > tbody tr:nth-child(' + index + ') td:nth-child(3) input').val() }; } var request = { 'location': $('#location :selected').val(), 'operations': operations }; client.invokeApi('calculator', { 'method': 'POST', 'body': request }) .done(function (response) { if (Array.isArray(response.result) && response.result.length > 0) { var results = response.result; for (var j = 0; j < results.length; j++) { $('#operations > tbody tr:nth-child(' + (j + 1) +') td:nth-child(4) input').val(results[j].result); } $('#summary').html('Operations successfully calculated.'); } }, function (error) { var text = error + (error.request ? ' - ' + error.request.status : ''); $('#errorlog').append($('<li>').text(text)); } ); } // Handle insert $('#add-operation').submit(function (event) { var operand1 = $('#operand1').val(); var operand2 = $('#operand2').val(); var operator = $('#operator').val(); if (operand1 === '') { $('#summary').html('<strong>Operand 1</strong> cannot be null.'); event.preventDefault(); return; } if (operand2 === '') { $('#summary').html('<strong>Operand 2</strong> cannot be null.'); event.preventDefault(); return; } if (!$.isNumeric(operand1)) { $('#summary').html('<strong>Operand 1</strong> must be a number.'); event.preventDefault(); return; } if (!$.isNumeric(operand2)) { $('#summary').html('<strong>Operand 2</strong> must be a number.'); event.preventDefault(); return; } var row = $('<tr>').append($('<td><input class="operation-text" value="' + operand1 + '"/></td>')) .append($('<td><select class="operation-select"><option' + (operator === '+' ?
    ' selected="selected"' : '') + '>+</option>' + '<option' + (operator === '-' ? ' selected="selected"' : '') + '>-</option>' + '<option' + (operator === '*' ? ' selected="selected"' : '') + '>*</option>' + '<option' + (operator === '/' ? ' selected="selected"' : '') + '>/</option>' + '</select></td>')) .append($('<td><input class="operation-text" value="' + operand2 + '"/></td>')) .append($('<td><input class="operation-text" value=""/></td>')) .append($('<td><button class="button-delete">Delete</button></td>')).append('</tr>'); $('#operations > tbody').append(row).toggle(true); $('#summary').html('<strong>' + operand1 + ' ' + operator + ' ' + operand2 +
    '</strong> added to the operation list.'); $('#operand1').val(''); $('#operand2').val(''); event.preventDefault(); }); // Handle delete $(document.body).on('click', '.button-delete', function (event) { var operand1 = $(this).closest('tr').find('td:nth-child(1) input').val(); var operator = $(this).closest('tr').find('td:nth-child(2) select :selected').val(); var operand2 = $(this).closest('tr').find('td:nth-child(3) input').val(); var row = $(this).parents('tr').first(); if (row) { row.remove(); $('#summary').html('<strong>' + operand1 + ' ' + operator + ' ' + operand2 +
    '</strong> deleted from the operation list.'); event.preventDefault(); } }); // Handle send $(document.body).on('click', '.button-send', function (event) { sendOperations(); event.preventDefault(); }); // Handle clear $(document.body).on('click', '.button-clear', function (event) { $('#operations tbody').html(''); $('#summary').html('Operation list cleared.'); event.preventDefault(); }); // Handle keydown event for operand input fields $(document.body).on('keydown', '.operation-text', function (event) { var key = event.charCode || event.keyCode || 0; // allow backspace, tab, delete, arrows, numbers // and keypad numbers ONLY return ( key == 8 || key == 9 || key == 46 || key == 189 || key == 190 || (key >= 37 && key <= 40) || (key >= 48 && key <= 57) || (key >= 96 && key <= 105)); }); });
    NOTE: make sure to replace the following placeholders in the page.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 Windows Store app uses the InvokeApiAsync methods provided by the MobileServiceClient class.
    The following table contains the code of the MainPage.xaml.cs class that contains the client-side logic used to invoke the mobile service.
    #region Using Directives
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using Microsoft.WindowsAzure.MobileServices;
    using Newtonsoft.Json;
    using System;
    using Windows.System;
    using Windows.UI.Popups;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Navigation; 
    #endregion
    
    namespace WindowsStoreApp
    {
      public class Request
      {
        [JsonProperty(PropertyName = "location", Order = 1)]
        public string Location { get; set; }
        [JsonProperty(PropertyName = "operations", Order = 2)]
        public ObservableCollection<Operation> Operations { get; set; }
      }
    
      public class Operation :  INotifyPropertyChanged
      {
        #region Private Fields
        private double op1;
        private string op;
        private double op2;
        #endregion
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
          var handler = PropertyChanged;
          if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        protected bool SetField<T>(ref T field, T value, string propertyName)
        {
          if (EqualityComparer<T>.Default.Equals(field, value)) return false;
          field = value;
          OnPropertyChanged(propertyName);
          return true;
        }
        [JsonProperty(PropertyName = "op1", Order = 1)]
        public double Operand1
        {
          get { return op1; }
          set { SetField(ref op1, value, "Operand1"); }
        }
        [JsonProperty(PropertyName = "op", Order = 2)]
        public string Operator
        {
          get { return op; }
          set { SetField(ref op, value, "Operator"); }
        }
        [JsonProperty(PropertyName = "op2", Order = 3)]
        public double Operand2
        {
          get { return op2; }
          set { SetField(ref op2, value, "Operand2"); }
        }
        [JsonProperty(PropertyName = "result", Order = 4)]
        public double? Result { get; set; }
      }
    
      public sealed partial class MainPage
      {
        private ObservableCollection<Operation> operations = new ObservableCollection<Operation>();
        private const string ApiName = "calculator";
        public MainPage()
        {
          InitializeComponent();
        }
    
        private async void SendOperations_Click(object sender, RoutedEventArgs e)
        {
          MobileServiceInvalidOperationException exception = null;
          var comboBoxItem = (ComboBoxItem)Location.SelectedItem;
          string location = null;
          if (comboBoxItem != null)
          {
            location = comboBoxItem.Content as string;
          }
          var request = new Request {Location = location, Operations = operations};
          try
          {
            Summary.Text = string.Format("Sending operations to {0} calculator service.", location);
            operations = new ObservableCollection<Operation>(
    await App.MobileService.InvokeApiAsync<Request, Operation[]>(ApiName, request)); OperationListView.ItemsSource = operations; Summary.Text = string.Format("Operations successfully processed by {0} calculator service.", location); } catch (MobileServiceInvalidOperationException ex) { Summary.Text = ex.Message; exception = ex; } if (exception != null) { await new MessageDialog(exception.Message, "Error processing operations").ShowAsync(); } } private void DeleteOperation_OnClick(object sender, RoutedEventArgs e) { var stackPanel = (StackPanel) ((Button) sender).Parent; if (stackPanel == null || !(stackPanel.DataContext is Operation)) { return; } var operation = stackPanel.DataContext as Operation; operations.Remove(operation); } private void AddOperation_Click(object sender, RoutedEventArgs e) { var comboBoxItem = (ComboBoxItem) Operator.SelectedItem; string op = null; if (comboBoxItem != null) { op = comboBoxItem.Content as string; } double operand1; double operand2; if (!double.TryParse(Operand1.Text, out operand1)) { return; } if (!double.TryParse(Operand2.Text, out operand2)) { return; } operations.Add(new Operation { Operand1 = operand1, Operator = op, Operand2 = operand2 }); Operand1.Text = string.Empty; Operand2.Text = string.Empty; } protected override void OnNavigatedTo(NavigationEventArgs e) { OperationListView.ItemsSource = operations; } private void Operand_KeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e) { if (((uint)e.Key >= (uint)VirtualKey.Number0 && (uint)e.Key <= (uint)VirtualKey.Number9) || (uint)e.Key == 189 || (uint)e.Key == 190) { e.Handled = false; } else e.Handled = true; } private void ClearOperations_Click(object sender, RoutedEventArgs e) { operations.Clear(); } } }
     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. In particular, this solution shows how to integrate Windows Azure Mobile Service with line of business applications, running on-premises or in the cloud, via Windows Azure BizTalk Services and Service Bus Relayed Messaging. Besides, this solution shows how to use Access Control Service to authenticate Mobile Services against an XML Request-Reply Bridge. You can download the code from MSDN Code Gallery. See also the following articles on Windows Azure Mobile Services: