Paolo Salvatori's Blog

Adventures in the magic world of Windows Azure

How to integrate a Windows Azure Web Site with a LOB app via a Service Bus Relay Service

How to integrate a Windows Azure Web Site with a LOB app via a Service Bus Relay Service

Rate This
  • Comments 4

Introduction

This sample demonstrates how to integrate an ASP.NET Web API REST service running in a Windows Azure Web Site with a line of business application, running on-premises or in the cloud, via Service Bus Relayed Messaging. The Access Control Service is used to authenticate the client and service application, in this case, the web site and LOB application. In addition, the solution demonstrates how to use a Notification Hub to send a push notification to mobile applications to advice that a new item is available in the products database. For more information on Notification Hubs, see the following resources:

You can download the code from MSDN Code Gallery.

Scenario

A RESTful web service running in a Windows Azure Web Site receives CRUD operations from a client application (Windows Phone 8 app, Windows Store app, HTML5/JS web site), but instead of accessing data stored in a Windows Azure SQL Database, it invokes a LOB application running in a corporate data center via Service Bus Relayed Messaging. In this sample, the LOB application is represented by a WCF service that accesses data from the ProductDb database hosted by a local instance of SQL Server 2012. In particular, the WCF service uses the new asynchronous programming feature provided by ADO.NET 4.5 to access data from the underlying database and exposes three different endpoints on the Service Bus, each using a different binding:

The RESTful web service is implemented as a Web API Controller class and runs within an ASP.NET MVC 4 web site that exposes a HTML5/JS user interface that can be accessed using an internet browser.

The following articles explain how to implement the same scenario using a Mobile Service in place of the Windows Azure Web Site and ASP.NET Web API RESTful service.

Architecture

The following diagram shows the architecture of the solution.
   
Message Flow
  1. The client application (Windows Phone 8 appWindows Store app or HTML5/JavaScript web site) sends a request via HTTPS to the RESTful web service that runs in a Windows Azure Web Site. The RESTful web service is implemented by the ProductsController class that is an ASP.NET Web API Controller, and as such it inherits from the ApiController class. Both the Windows Phone 8 app and Windows Store app use the HttpClient class to invoke the RESTful web service, while the HTML5/JavaScript web site uses a the jQuery.ajax(). The ASP.NET Web API Controllerimplements CRUD methods to create, read, update and delete data in the products repository. The HTTP method used by the client application to call the controller class depends on the invoked operation:
    • Read: GET method
    • Add: POST method
    • Update: POST method
    • Delete: DELETE method
  2. When the ProductsController class receives a request from a client application, it calls the ProductRepository class that implements the data access layer. This class can access data stored in-process in a static structure (this mode was created for testing purposes) or in a remote ProductDb database via Windows Azure Service Bus. You can control which of the two repositories is used by the ProductRepository class selecting a value in the Location drop down list on the HTML5/JS web site: when the selected value is equal to Local, the controller uses a static List<Product> structure for CRUD operations, while if the value is equal to ServiceBus, the controller class invokes the WCF wervice running on-premises via Service Bus Relayed Messaging. In this case, the ProductRepository class uses one of the 3 WCF client endpoints defined in the Web.config file to call the WCF service running on-premises via Service Bus Relayed Messaging.
  3. The Service Bus Relay Service validates and remove the security token, then forwards the request to one the listeners hosting the WCF service.
  4. The WCF service uses the new asynchronous programming feature provided by ADO.NET 4.5 to access data from the ProductDb database. For demo purpose, the WCF service runs in a console application, but the sample can easily be modified to host the service in IIS.
  5. The WCF service returns a response message to the Service Bus Relay Service.
  6. The Service Bus Relay Service forwards the message to the ASP.NET Web API RESTful service running in the Windows Azure Web Site
  7. The ASP.NET Web API RESTful service returns data in JSON format to the client application. When the operation performed adds a new item to the products repository, the ProductsController class uses a NotificationHubClient class to send a push notification to the Windows Phone 8 and Windows Store apps.

Prerequisites

  • Visual Studio 2012 Express for Windows 8
  • Windows Azure account (get the Free Trial

Building the Sample

Proceed as follows to set up the solution.

Configure your Notification Hub

  1. Log on to the Windows Azure Management Portal, and click NEW at the bottom of the screen.

  2. Click on App Services, then Service Bus, then Notification Hub, then Quick Create.

  3. Type a name for your notification hub, select your desired Region, and then click Create a new Notification Hub.

  4. Click the namespace you just created (usually notification hub name-ns), then click the Configure tab at the top.

  5. Select the tab Notification Hubs at the top, and then click the notification hub you just created.

  6. Select the tab Configure at the top, enter the Client secret and Package SID for your Windows Store App in the Windows notification settings, and then click Save. Repeat the same operation for Windows Phone notification settings, Apple Notification Settings, Google cloud messaging settings, if you plan to use any of these push notification services. For more information on how to configure a Notification Hub, see Getting Started with Notification Hubs.

  7. Select the tab Dashboard at the top, and then click Connection Information. Take note of the two connection strings.

Your notification hub is now configured to work with the specified push notification services and you have the connection strings to register your app and send notifications.

Visual Studio Solution

The Visual Studio solution includes the following projects:
  • ProductDb.sql: this script can be used to create the SQL Server database used by the WCF service to store data.
  • Contracts: contains the service and data contracts used by the WCF service.
  • Service: contains the WCF service that represents the line of business application invoked by the ASP.NET Web API RESTful service. The service runs within a console application, but the project can be easily changed to host the service in IIS.
  • Client: contains a console application that you can use to test the 3 endpoints exposed by the WCF Service Bus Relay Service.
  • ProductService: contains the ASP.NET MVC 4 application that implements the HTML5/JS user interface and the ASP.NET Web API RESTful services.
  • WindowsPhone8: contains the Windows Phone 8 app that can be used to test the ASP.NET Web API RESTful service.
  • WindowsStoreApp: contains the Windows Store app that can be used to test the ASP.NET Web API RESTful service.

NOTE: most of the projects in the solution use NuGet packages. To reduce the size of the zip file, I deleted some of the assemblies 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.

SQL Server Database

Run the ProductDb.sql script to create the database used by the solution and some sample data:
USE master
GO
-- Create ProductDb database
DECLARE @Count int
SELECT @Count = COUNT(*) FROM sysdatabases 
WHERE [name] = 'ProductDb'
IF (@Count = 0) 
CREATE DATABASE ProductDb
GO
USE ProductDb
GO
IF OBJECT_ID('Products') > 0 DROP TABLE [Products]
GO
-- Create Products table
CREATE TABLE [Products] (
    [ProductID] [int] IDENTITY(1,1) NOT NULL ,
    [Name] [nvarchar](50) NOT NULL ,
    [Category] [nvarchar](50) NOT NULL ,
    [Price] [smallmoney] NOT NULL
    CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED 
    (
        [ProductID]
    )
)
GO
-- Create stored procedures
IF OBJECT_ID('GetProduct') > 0 DROP PROCEDURE [GetProduct]
GO
CREATE PROCEDURE GetProduct
@ProductID int
AS
SELECT [ProductID], [Name], [Category], [Price] 
FROM [Products] 
WHERE [ProductID] = @ProductID
GO
IF OBJECT_ID('GetProducts') > 0 DROP PROCEDURE [GetProducts]
GO
CREATE PROCEDURE GetProducts
AS
SELECT [ProductID], [Name], [Category], [Price]  
FROM [Products] 
GO
IF OBJECT_ID('GetProductsByCategory') > 0 DROP PROCEDURE [GetProductsByCategory]
GO
CREATE PROCEDURE GetProductsByCategory
@Category [nvarchar](50)
AS
SELECT [ProductID], [Name], [Category], [Price]  
FROM [Products] 
WHERE [Category] = @Category
GO
IF OBJECT_ID('AddProduct') > 0 DROP PROCEDURE [AddProduct]
GO
CREATE PROCEDURE AddProduct
@ProductID int OUTPUT,
@Name [nvarchar](50),
@Category [nvarchar](50),
@Price [smallmoney]
AS
INSERT INTO Products VALUES (@Name, @Category, @Price)
SET @ProductID = @@IDENTITY
GO
IF OBJECT_ID('UpdateProduct') > 0 DROP PROCEDURE [UpdateProduct]
GO
CREATE PROCEDURE UpdateProduct
@ProductID int,
@Name [nvarchar](50),
@Category [nvarchar](50),
@Price [smallmoney]
AS
UPDATE Products 
SET [Name] = @Name,
    [Category] = @Category,
    [Price] = @Price
WHERE [ProductID] = @ProductID
GO
IF OBJECT_ID('DeleteProduct') > 0 DROP PROCEDURE [DeleteProduct]
GO
CREATE PROCEDURE DeleteProduct
@ProductID int
AS
DELETE [Products]
WHERE [ProductID] = @ProductID
GO
-- Create test data
SET NOCOUNT ON
GO
INSERT INTO Products VALUES (N'Tomato soup', N'Groceries', 1.39)
GO
INSERT INTO Products VALUES (N'Babo', N'Toys', 19.99)
GO
INSERT INTO Products VALUES (N'Hammer', N'Hardware', 16.49)
GO

Contracts

The following table contains the code of the IProductService interface that represents the service contract implemented by the service.
namespace Contracts
{
    [ServiceContract(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
    public interface IProductService
    {
        [WebGet(UriTemplate = "getproduct?id={productId}", 
                BodyStyle = WebMessageBodyStyle.Bare, 
                ResponseFormat = WebMessageFormat.Json)]
        [OperationContract(Action = "getProduct", 
                           ReplyAction = "getProductResponse")]
        [return: MessageParameter(Name = "product")]
        Product GetProduct([MessageParameter(Name = "productId")]int productId);

        [WebGet(UriTemplate = "getproducts?category={category}", 
                BodyStyle = WebMessageBodyStyle.Bare, 
                ResponseFormat = WebMessageFormat.Json)]
        [OperationContract(Action = "getProductsByCategory", 
                           ReplyAction = "getProductsByCategoryResponse")]
        [return: MessageParameter(Name = "products")]
        List<Product> GetProductsByCategory([MessageParameter(Name = "category")]string category);

        [WebGet(UriTemplate = "getproducts", 
                BodyStyle = WebMessageBodyStyle.Bare, 
                ResponseFormat = WebMessageFormat.Json)]
        [OperationContract(Action = "getProducts", 
                           ReplyAction = "getProductsResponse")]
        [return: MessageParameter(Name = "products")]
        List<Product> GetProducts();

        [WebInvoke(Method = "DELETE", 
                   UriTemplate = "deleteproduct?id={productId}")]
        [OperationContract(Action = "deleteProduct", 
                           ReplyAction = "deleteProductResponse")]
        void DeleteProduct([MessageParameter(Name = "productId")]int productId);

        [WebInvoke(Method = "POST", 
                   UriTemplate = "addproduct", 
                   RequestFormat = WebMessageFormat.Json, 
                   ResponseFormat = WebMessageFormat.Json)]
        [OperationContract(Action = "addProduct", 
                           ReplyAction = "addProductResponse")]
        [return: MessageParameter(Name = "product")]
        Product AddProduct([MessageParameter(Name = "product")]Product product);

        [WebInvoke(Method = "PATCH", 
                   UriTemplate = "updateproduct", 
                   RequestFormat = WebMessageFormat.Json, 
                   ResponseFormat = WebMessageFormat.Json)]
        [OperationContract(Action = "updateProduct", 
                           ReplyAction = "updateProductResponse")]
        [return: MessageParameter(Name = "product")]
        Product UpdateProduct([MessageParameter(Name="product")]Product product);
    }
}
The following table contains the code of the Product class:
#region Using Directives
using System.Runtime.Serialization;
using Newtonsoft.Json;
#endregion

namespace Contracts
{
    [DataContract(Name = "product", 
                  Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
    public class Product
    {
        #region Public Properties
        [JsonProperty(PropertyName = "productId", Order = 1)]
        [DataMember(Name = "productId", Order = 1)]
        public int ProductId { get; set; }
        
        [JsonProperty(PropertyName = "name", Order = 2)]
        [DataMember(Name = "name", Order = 2)]
        public string Name { get; set; }
        
        [JsonProperty(PropertyName = "category", Order = 3)]
        [DataMember(Name = "category", Order = 3)]
        public string Category { get; set; }
        
        [JsonProperty(PropertyName = "price", Order = 4)]
        [DataMember(Name = "price", Order = 4)]
        public decimal Price { get; set; }
        #endregion
    }
}

Service

The service project contains the code of the ProductService invoked by the ASP.NET Web API RESTful service.
The following table contains the code of the ProductService class.
#region Using Directives 
using System.Collections.Generic; 
using System.ServiceModel; 
using Contracts; 
#endregion 
 
namespace Service 
{ 
    [ServiceBehavior(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")] 
    public class ProductService : IProductService 
    { 
       #region Public Methods 
        public  Product GetProduct(int id) 
        { 
            return ProductRepository.GetProduct(id).Result; 
        } 
         
        public List<Product> GetProductsByCategory(string category) 
        { 
            return ProductRepository.GetProductsByCategory(category).Result; 
        } 
 
        public List<Product> GetProducts() 
        { 
            return ProductRepository.GetProducts().Result; 
        } 
 
        public void DeleteProduct(int id) 
        { 
            ProductRepository.DeleteProduct(id).Wait(); 
        } 
 
        public Product AddProduct(Product product) 
        { 
            return ProductRepository.AddProduct(product).Result; 
        } 
 
        public Product UpdateProduct(Product product) 
        { 
            return ProductRepository.UpdateProduct(product).Result; 
        } 
       #endregion 
    } 
}
The following table contains the code of the ProductRepository class. This class uses the asynchronous programming functionality provided by ADO.NET 4.5 to access data from the ProductDb.
#region Using Directives 
using System; 
using System.Collections.Generic; 
using System.Configuration; 
using System.Data; 
using System.Data.SqlClient; 
using System.Diagnostics; 
using System.Threading.Tasks; 
using Contracts; 
#endregion 
 
namespace Service 
{ 
  public static class ProductRepository 
  { 
    #region Private Static Fields 
    private static readonly string line = new string('-', 99); 
    private static readonly string connectionString; 
    #endregion 
 
    #region Static Constructor 
    static ProductRepository() 
    { 
      var setting = ConfigurationManager.ConnectionStrings["ProductDb"]; 
      if (setting != null) 
      { 
        connectionString = setting.ConnectionString; 
      } 
    } 
    #endregion 
 
    #region Public Methods 
    public static async Task<Product> GetProduct(int id) 
    { 
      try 
      { 
        Trace.WriteLine(string.Format("GetProduct: Id=[{0}]", id)); 
        Trace.WriteLine(line); 
        using (var connection = new SqlConnection(connectionString)) 
        { 
          await connection.OpenAsync(); 
          using (var command = new SqlCommand("GetProduct", connection)) 
          { 
            command.CommandType = CommandType.StoredProcedure; 
            command.CommandTimeout = 60; 
            command.Parameters.Add(new SqlParameter 
              { 
                ParameterName = "@ProductID", 
                Direction = ParameterDirection.Input, 
                SqlDbType = SqlDbType.Int, 
                Value = id 
              }); 
            var reader = await command.ExecuteReaderAsync(); 
            while (await reader.ReadAsync()) 
            { 
              return new Product 
              { 
                ProductId = await reader.GetFieldValueAsync<int>(0), 
                Name = await reader.IsDBNullAsync(1) ? string.Empty : await reader.GetFieldValueAsync<string>(1), 
                Category = await reader.IsDBNullAsync(2) ? string.Empty : await reader.GetFieldValueAsync<string>(2), 
                Price = await reader.IsDBNullAsync(3) ? 0 : await reader.GetFieldValueAsync<decimal>(3) 
              }; 
            } 
          } 
        } 
        return null; 
      } 
      catch (Exception ex) 
      { 
        Console.WriteLine(ex.Message); 
        throw; 
      } 
    } 
 
    public static async Task<List<Product>> GetProductsByCategory(string category) 
    { 
      try 
      { 
        if (string.IsNullOrEmpty(category)) 
        { 
          throw new ArgumentException("The category paramater cannot be null or empty."); 
        } 
        Trace.WriteLine(string.Format("GetProductsByCategory: Category=[{0}]", category)); 
        Trace.WriteLine(line); 
        using (var connection = new SqlConnection(connectionString)) 
        { 
          await connection.OpenAsync(); 
          using (var command = new SqlCommand("GetProductsByCategory", connection)) 
          { 
            command.CommandType = CommandType.StoredProcedure; 
            command.CommandTimeout = 60; 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@Category", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.NVarChar, 
              Size = 50, 
              Value = category 
            }); 
            var reader = await command.ExecuteReaderAsync(); 
            var productList = new List<Product>(); 
            while (await reader.ReadAsync()) 
            { 
              productList.Add(new Product 
              { 
                ProductId = await reader.GetFieldValueAsync<int>(0), 
                Name = await reader.IsDBNullAsync(1) ? string.Empty : await reader.GetFieldValueAsync<string>(1), 
                Category = await reader.IsDBNullAsync(2) ? string.Empty : await reader.GetFieldValueAsync<string>(2), 
                Price = await reader.IsDBNullAsync(3) ? 0 : await reader.GetFieldValueAsync<decimal>(3) 
              }); 
            } 
            return productList; 
          } 
        } 
      } 
      catch (Exception ex) 
      { 
        Console.WriteLine(ex.Message); 
        throw; 
      } 
    } 
 
    public static async Task<List<Product>> GetProducts() 
    { 
      try 
      { 
        Trace.WriteLine("GetProducts"); 
        Trace.WriteLine(line); 
        using (var connection = new SqlConnection(connectionString)) 
        { 
          await connection.OpenAsync(); 
          using (var command = new SqlCommand("GetProducts", connection)) 
          { 
            command.CommandType = CommandType.StoredProcedure; 
            command.CommandTimeout = 60; 
            var reader = await command.ExecuteReaderAsync(); 
            var productList = new List<Product>(); 
            while (await reader.ReadAsync()) 
            { 
              productList.Add(new Product 
              { 
                ProductId = await reader.GetFieldValueAsync<int>(0), 
                Name = await reader.IsDBNullAsync(1) ? string.Empty : await reader.GetFieldValueAsync<string>(1), 
                Category = await reader.IsDBNullAsync(2) ? string.Empty : await reader.GetFieldValueAsync<string>(2), 
                Price = await reader.IsDBNullAsync(3) ? 0 : await reader.GetFieldValueAsync<decimal>(3) 
              }); 
            } 
            return productList; 
          } 
        } 
      } 
      catch (Exception ex) 
      { 
        Console.WriteLine(ex.Message); 
        throw; 
      } 
    } 
 
    public static async Task<int> DeleteProduct(int id) 
    { 
      try 
      { 
        Trace.WriteLine(string.Format("DeleteProduct: Id=[{0}]", id)); 
        Trace.WriteLine(line); 
        using (var connection = new SqlConnection(connectionString)) 
        { 
          await connection.OpenAsync(); 
          using (var command = new SqlCommand("DeleteProduct", connection)) 
          { 
            command.CommandType = CommandType.StoredProcedure; 
            command.CommandTimeout = 60; 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@ProductID", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.Int, 
              Value = id 
            }); 
            return await command.ExecuteNonQueryAsync(); 
          } 
        } 
      } 
      catch (Exception ex) 
      { 
        Console.WriteLine(ex.Message); 
        throw; 
      } 
    } 
 
    public static async Task<Product> AddProduct(Product product) 
    { 
      try 
      { 
        if (product == null) 
        { 
          throw new ArgumentException("The product paramater cannot be null."); 
        } 
        Trace.WriteLine(string.Format("AddProduct: Name=[{0}] Category=[{1}] Price=[{2}]",  
                       product.Name, 
                       product.Category, 
                       product.Price)); 
        Trace.WriteLine(line); 
        using (var connection = new SqlConnection(connectionString)) 
        { 
          await connection.OpenAsync(); 
          using (var command = new SqlCommand("AddProduct", connection)) 
          { 
            command.CommandType = CommandType.StoredProcedure; 
            command.CommandTimeout = 60; 
            var productIdParam = new SqlParameter 
              { 
                ParameterName = "@ProductID", 
                Direction = ParameterDirection.Output, 
                SqlDbType = SqlDbType.Int 
              }; 
            command.Parameters.Add(productIdParam); 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@Name", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.NVarChar, 
              Size = 50, 
              Value = product.Name 
            }); 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@Category", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.NVarChar, 
              Size = 50, 
              Value = product.Category 
            }); 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@Price", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.SmallMoney, 
              Value = product.Price 
            }); 
            await command.ExecuteNonQueryAsync(); 
            product.ProductId = (int)productIdParam.Value; 
            return product; 
          } 
        } 
      } 
      catch (Exception ex) 
      { 
        Console.WriteLine(ex.Message); 
        throw; 
      } 
    } 
 
    public static async Task<Product> UpdateProduct(Product product) 
    { 
      try 
      { 
        if (product == null) 
        { 
          throw new ArgumentException("The product paramater cannot be null."); 
        } 
        Trace.WriteLine(string.Format("UpdateProduct: Id=[{0}] Name=[{1}] Category=[{2}] Price=[{3}]", 
                       product.ProductId, 
                       product.Name, 
                       product.Category, 
                       product.Price)); 
        Trace.WriteLine(line); 
        using (var connection = new SqlConnection(connectionString)) 
        { 
          await connection.OpenAsync(); 
          using (var command = new SqlCommand("UpdateProduct", connection)) 
          { 
            command.CommandType = CommandType.StoredProcedure; 
            command.CommandTimeout = 60; 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@ProductID", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.Int, 
              Value = product.ProductId 
            }); 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@Name", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.NVarChar, 
              Size = 50, 
              Value = product.Name 
            }); 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@Category", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.NVarChar, 
              Size = 50, 
              Value = product.Category 
            }); 
            command.Parameters.Add(new SqlParameter 
            { 
              ParameterName = "@Price", 
              Direction = ParameterDirection.Input, 
              SqlDbType = SqlDbType.SmallMoney, 
              Value = product.Price 
            }); 
            await command.ExecuteNonQueryAsync(); 
            return product; 
          } 
        } 
      } 
      catch (Exception ex) 
      { 
        Console.WriteLine(ex.Message); 
        throw; 
      } 
    } 
    #endregion 
  } 
}
Finally, the following table contains the configuration file of the service. As you can easily notice, the service exposes three different endpoints that in turn use three different bindings:
The ASP.NET Web API RESTful service is configured to invoke the NetTcpRelayBinding endpoint, but, as explained below, you can change the endpoint used to invoke the Service Bus Relay Service.
<?xml version="1.0"?> 
<configuration> 
  <connectionStrings> 
    <add name="ProductDb" connectionString="Server=(local);Database=ProductDb;Integrated Security=true;Async=true;Connection Timeout=30" /> 
  </connectionStrings> 
  <system.diagnostics> 
    <trace autoflush="false" indentsize="4"> 
      <listeners> 
        <add name="configConsoleListener" 
          type="System.Diagnostics.ConsoleTraceListener" /> 
      </listeners> 
    </trace> 
    <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:\Demos\Service.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="false"  
                      logMalformedMessages="false"  
                      logMessagesAtServiceLevel="false"  
                      logMessagesAtTransportLevel="false"/> 
    </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> 
      <serviceBehaviors> 
        <behavior name="serviceBehavior"> 
          <serviceDebug includeExceptionDetailInFaults="true"/> 
        </behavior> 
      </serviceBehaviors> 
      <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" defaultOutgoingResponseFormat="Json"/> 
          <transportClientEndpointBehavior> 
            <tokenProvider> 
              <sharedSecret issuerName="owner" issuerSecret="[YOUR-ISSUER-SECRET]"/> 
            </tokenProvider> 
          </transportClientEndpointBehavior> 
          <serviceRegistrySettings discoveryMode="Public" displayName="config"/> 
        </behavior> 
      </endpointBehaviors> 
    </behaviors> 
    <!-- Service Conbfiguration --> 
    <services> 
      <service name="Service.ProductService" behaviorConfiguration="serviceBehavior"> 
        <endpoint address="sb://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/nettcp"  
                  behaviorConfiguration="serviceBusEndpointBehavior"  
                  binding="netTcpRelayBinding"  
                  bindingConfiguration="netTcpRelayBinding"  
                  contract="Contracts.IProductService"  
                  name="netTcpRelayBinding"/> 
        <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/basichttp" 
                   behaviorConfiguration="serviceBusEndpointBehavior" 
                   binding="basicHttpRelayBinding" 
                   bindingConfiguration="basicHttpRelayBinding" 
                   contract="Contracts.IProductService" 
                   name="basicHttpRelayBinding"/> 
        <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/webhttp" 
                  behaviorConfiguration="webHttpEndpointBehavior" 
                  binding="webHttpRelayBinding" 
                  bindingConfiguration="webHttpRelayBinding" 
                  contract="Contracts.IProductService" 
                  name="webHttpRelayBinding"/> 
      </service> 
    </services> 
    <extensions> 
      <!-- In this extension section we are introducing all known service bus extensions. User can remove the ones they don't need. --> 
      <behaviorExtensions> 
        <add name="connectionStatusBehavior"  
             type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="transportClientEndpointBehavior"  
             type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="serviceRegistrySettings"  
             type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
      </behaviorExtensions> 
      <bindingElementExtensions> 
        <add name="netMessagingTransport"  
             type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="tcpRelayTransport"  
             type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="httpRelayTransport"  
        type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="httpsRelayTransport"  
        type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="onewayRelayTransport"  
        type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
      </bindingElementExtensions> 
      <bindingExtensions> 
        <add name="basicHttpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="webHttpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="ws2007HttpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netTcpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netOnewayRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netEventRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netMessagingBinding"  
             type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, Microsoft.ServiceBus, 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.

Client

This project contains a console application that you can use to test the three endpoints exposed by the ProductService.
The following table contains the code of the Program.cs class.
#region Using Directives 
using System; 
using System.Linq; 
using System.Configuration; 
using System.ServiceModel; 
using System.ServiceModel.Configuration; 
using Contracts; 
#endregion 
 
namespace Client 
{ 
  internal class Program 
  { 
    static readonly string line = new string('-', 99); 
 
    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); 
             
            var factory = new ChannelFactory<IProductService>(endpoint); 
            var channel = factory.CreateChannel(); 
 
            // Add Product 
            Console.WriteLine("Add Product"); 
            Console.WriteLine(line); 
            var product = new Product {Name="Jeero", Category = "Toys", Price = (decimal)19.99}; 
            product = channel.AddProduct(product); 
            if (product == null) 
            { 
              PrintErrorMessage(); 
              return; 
            } 
            PrintProduct(product); 
            Console.WriteLine(line); 
 
            // Update Product 
            Console.WriteLine("Uppdate Product"); 
            Console.WriteLine(line); 
            product.Name = "Groody"; 
            product = channel.UpdateProduct(product); 
            if (product == null) 
            { 
              PrintErrorMessage(); 
              return; 
            } 
            PrintProduct(product); 
            Console.WriteLine(line); 
 
            // Get Product 
            Console.WriteLine("Get Product"); 
            Console.WriteLine(line); 
            product = channel.GetProduct(product.ProductId); 
            if (product == null) 
            { 
              PrintErrorMessage(); 
              return; 
            } 
            PrintProduct(product); 
            Console.WriteLine(line); 
 
            // Get List<Product> By Category 
            Console.Write("Get Products By Category: "); 
            Console.ForegroundColor = ConsoleColor.Yellow; 
            Console.Write("Category"); 
            Console.ResetColor(); 
            Console.WriteLine("=Toys "); 
            Console.WriteLine(line); 
            var products = channel.GetProductsByCategory("Toys"); 
            foreach (var item in products) 
            { 
              PrintProduct(item); 
            } 
            Console.WriteLine(line); 
 
            // Delete Product 
            Console.WriteLine("Delete Product"); 
            Console.WriteLine(line); 
            channel.DeleteProduct(product.ProductId); 
            PrintProductId(product); 
            Console.WriteLine(line); 
 
            // Get List<Product> 
            Console.WriteLine("Get Products"); 
            Console.WriteLine(line); 
            products = channel.GetProducts(); 
            foreach (var item in products) 
            { 
              PrintProduct(item); 
            } 
            Console.WriteLine(line); 
          } 
          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 = 100; 
      Console.BufferWidth = 100; 
      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 = 'a'; 
      while (key < '1' || key > '9') 
      { 
        key = Console.ReadKey(true).KeyChar; 
      } 
      Console.WriteLine(); 
      return key - '1' == endpointList.Count ? null : endpointList[key - '1'].Name; 
    } 
 
    private static void PrintProduct(Product product) 
    { 
      Console.ForegroundColor = ConsoleColor.Yellow; 
      Console.Write("ProductId"); 
      Console.ResetColor(); 
      Console.Write("={0} ", product.ProductId); 
      Console.ForegroundColor = ConsoleColor.Yellow; 
      Console.Write("Name"); 
      Console.ResetColor(); 
      Console.Write("={0} ", product.Name); 
      Console.ForegroundColor = ConsoleColor.Yellow; 
      Console.Write("Category"); 
      Console.ResetColor(); 
      Console.Write("={0} ", product.Category); 
      Console.ForegroundColor = ConsoleColor.Yellow; 
      Console.Write("Price"); 
      Console.ResetColor(); 
      Console.WriteLine("={0} ", product.Category); 
    } 
 
    private static void PrintProductId(Product product) 
    { 
      Console.ForegroundColor = ConsoleColor.Yellow; 
      Console.Write("ProductId"); 
      Console.ResetColor(); 
      Console.WriteLine("={0} ", product.ProductId); 
    } 
 
    private static void PrintErrorMessage() 
    { 
      Console.WriteLine("Something went wrong."); 
      Console.WriteLine("Press [Enter] to exit"); 
      Console.ReadLine(); 
    } 
  } 
}
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:\Demos\Client.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="false"  
                      logMessagesAtServiceLevel="true"  
                      logMessagesAtTransportLevel="false"/> 
    </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 --> 
    <client> 
      <endpoint address="sb://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/nettcp" 
                behaviorConfiguration="serviceBusEndpointBehavior" 
                binding="netTcpRelayBinding" 
                bindingConfiguration="netTcpRelayBinding" 
                contract="Contracts.IProductService" 
                name="netTcpRelayBinding"/> 
      <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/basichttp" 
                  behaviorConfiguration="serviceBusEndpointBehavior" 
                  binding="basicHttpRelayBinding" 
                  bindingConfiguration="basicHttpRelayBinding" 
                  contract="Contracts.IProductService" 
                  name="basicHttpRelayBinding"/> 
      <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/webhttp" 
                behaviorConfiguration="webHttpEndpointBehavior" 
                binding="webHttpRelayBinding" 
                bindingConfiguration="webHttpRelayBinding" 
                contract="Contracts.IProductService" 
                name="webHttpRelayBinding"/> 
    </client> 
    <extensions> 
      <!-- In this extension section we are introducing all known service bus extensions. User can remove the ones they don't need. --> 
      <behaviorExtensions> 
        <add name="connectionStatusBehavior"  
             type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="transportClientEndpointBehavior"  
             type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="serviceRegistrySettings"  
             type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
      </behaviorExtensions> 
      <bindingElementExtensions> 
        <add name="netMessagingTransport"  
             type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="tcpRelayTransport"  
             type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="httpRelayTransport"  
        type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="httpsRelayTransport"  
        type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="onewayRelayTransport"  
        type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
      </bindingElementExtensions> 
      <bindingExtensions> 
        <add name="basicHttpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="webHttpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="ws2007HttpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netTcpRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netOnewayRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netEventRelayBinding"  
             type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
        <add name="netMessagingBinding"  
             type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, Microsoft.ServiceBus, 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.

 

HTML5/JavaScript Client

The HTML5/JavaScript client application is composed by a single .cshtml page defined in a ASP.NET MVC 4 web site. The page directly calls the ASP.NET Web API RESTful services using the jQuery.ajax() function.

The following table contains the code the page.js script that contains the client-side logic used by the application to invoke the ASP.NET Web API RESTful service.

$(function () { 
  var productsServiceUrl = 'api/products'; 
  var repositoryServiceUrl = 'api/repository'; 
   
  // Cloud Animation 
  $('#far-clouds1').pan({ fps: 30, speed: 0.7, dir: 'left', depth: 30 }); 
  $('#near-clouds1').pan({ fps: 30, speed: 1, dir: 'left', depth: 70 }); 
  $('#far-clouds2').pan({ fps: 30, speed: 0.7, dir: 'left', depth: 100 }); 
  $('#near-clouds2').pan({ fps: 30, speed: 1, dir: 'left', depth: 130 }); 
 
  window.actions = { 
    speedyClouds: function () { 
      $('#far-clouds').spSpeed(12); 
      $('#near-clouds').spSpeed(20); 
    }, 
    runningClouds: function () { 
      $('#far-clouds').spSpeed(8); 
      $('#near-clouds').spSpeed(12); 
    }, 
    walkingClouds: function () { 
      $('#far-clouds').spSpeed(3); 
      $('#near-clouds').spSpeed(5); 
    }, 
    lazyClouds: function () { 
      $('#far-clouds').spSpeed(0.7); 
      $('#near-clouds').spSpeed(1); 
    }, 
    stop: function () { 
      $('#far-clouds, #near-clouds').spStop(); 
    }, 
    start: function () { 
      $('#far-clouds, #near-clouds').spStart(); 
    }, 
    toggle: function () { 
      $('#far-clouds, #near-clouds').spToggle(); 
    }, 
    left: function () { 
      $('#far-clouds, #near-clouds').spChangeDir('left'); 
    }, 
    right: function () { 
      $('#far-clouds, #near-clouds').spChangeDir('right'); 
    } 
  }; 
 
  // Read current data and rebuild UI. 
  // If you plan to generate complex UIs like this, consider using a JavaScript templating library. 
  function refreshProducts() { 
    $('#summary').html('Retrieving products...'); 
    $.ajax({ 
      url: productsServiceUrl, 
      type: 'GET', 
      dataType: 'json', 
      success: function (data) { 
        if (Array.isArray(data) && data.length > 0) { 
          var products = $.map(data, function (product) { 
            return $('<tr>').append($('<td><input class="product-text" style='width: 70px' value='' + product.productId + '"/></td>')) 
              .append($('<td><input class="product-text" value="' + product.name + '"/></td>')) 
              .append($('<td><input class="product-text" value="' + product.category + '"/></td>')) 
              .append($('<td><input class="product-text" style='width: 100px' value='' + parseFloat(product.price).toFixed(2) + '"/></td>')) 
              .append($('<td><button class="button-update" style='width: 70px' >Update</button></td>')) 
              .append($('<td><button class="button-delete" style='width: 70px' >Delete</button></td>')).append('</tr>'); 
          }); 
          $('#products > tbody').empty().append(products).toggle(products.length > 0); 
          $('#summary').html('<strong>' + products.length + '</strong> product(s) successfully retrieved.'); 
        } else { 
          $('#summary').html('No product retrieved.'); 
        } 
      }, 
      error: function (xhr, textStatus, error) { 
        var text = error + (error.request ? ' - ' + error.request.status : ''); 
        $('#errorlog').append($('<li>').text(text)); 
      } 
    }); 
  } 
 
  // Handle insert 
  $('#add-product').submit(function(event) { 
    if ($('#product-name').val() === '') { 
      $('#summary').html('The <strong>Name</strong> field cannot be null.'); 
      event.preventDefault(); 
      return; 
    } 
    if ($('#product-category').val() === '') { 
      $('#summary').html('The <strong>Category</strong> field cannot be null.'); 
      event.preventDefault(); 
      return; 
    } 
    if ($('#product-price').val() === '') { 
      $('#summary').html('The <strong>Price</strong> field cannot be null.'); 
      event.preventDefault(); 
      return; 
    } 
    var product = { 
      'name': $('#product-name').val(), 
      'category': $('#product-category').val(), 
      'price': $('#product-price').val() 
    }; 
    $('#summary').html('Adding <strong>' + product.name + '</strong>...'); 
    $.ajax({ 
      url: productsServiceUrl, 
      type: 'POST', 
      cache: false, 
      data: JSON.stringify(product), 
      dataType: 'json', 
      contentType: 'application/json; charset=utf-8', 
      success: function (data) { 
        if (data) { 
          var row = $('<tr>').append($('<td><input class="product-text" value="' + data.productId + '"/></td>')) 
            .append($('<td><input class="product-text" value="' + data.name + '"/></td>')) 
            .append($('<td><input class="product-text" value="' + data.category + '"/></td>')) 
            .append($('<td><input class="product-text" value="' + data.price + '"/></td>')) 
            .append($('<td><button class="button-update">Update</button></td>')) 
            .append($('<td><button class="button-delete">Delete</button></td>')).append('</tr>'); 
          $('#products > tbody').append(row).toggle(true); 
          $('#summary').html('<strong>' + data.name + '</strong> successfully added.'); 
        } 
      }, 
      error: function (xhr, textStatus, error) { 
        var text = error + (error.request ? ' - ' + error.request.status : ''); 
        $('#errorlog').append($('<li>').text(text)); 
      } 
    }); 
    $('#product-name').val(''); 
    $('#product-category').val(''); 
    $('#product-price').val(''); 
    event.preventDefault(); 
  }); 
 
  // Handle update 
  $(document.body).on('click', '.button-update', function (event) { 
    var row = $(this).closest('tr'); 
    var productId = row.find('td:nth-child(1) input').val(); 
    if (!productId) { 
      $('#summary').html('The <strong>product id</strong> field cannot be null.'); 
      event.preventDefault(); 
      return; 
    } 
    var name = row.find('td:nth-child(2) input').val(); 
    if (!name) { 
      $('#summary').html('The <strong>name</strong> field cannot be null.'); 
      event.preventDefault(); 
      return; 
    } 
    var category = row.find('td:nth-child(3) input').val(); 
    if (!category) { 
      $('#summary').html('The <strong>category</strong> field cannot be null.'); 
      event.preventDefault(); 
      return; 
    } 
    var price = row.find('td:nth-child(4) input').val(); 
    if (!price) { 
      $('#summary').html('The <strong>price</strong> field cannot be null.'); 
      event.preventDefault(); 
      return; 
    } 
    var product = { 
      'productId': productId, 
      'name': name, 
      'category': category, 
      'price': price 
    }; 
    $('#summary').html('Updating <strong>' + product.name + '</strong>...'); 
    $.ajax({ 
      url: productsServiceUrl + '/' + productId, 
      type: 'PUT', 
      cache: false, 
      data: JSON.stringify(product), 
      dataType: 'json', 
      contentType: 'application/json; charset=utf-8', 
      success: function () { 
        $('#summary').html('<strong>' + product.name + '</strong> successfully updated.'); 
      }, 
      error: function (xhr, textStatus, error) { 
        var text = error + (error.request ? ' - ' + error.request.status : ''); 
        $('#errorlog').append($('<li>').text(text)); 
      } 
    }); 
    event.preventDefault(); 
  }); 
 
  // Handle delete 
  $(document.body).on('click', '.button-delete', function (event) { 
    var productId = $(this).closest('tr').find('td:nth-child(1) input').val(); 
    var name = $(this).closest('tr').find('td:nth-child(2) input').val(); 
    var row = $(this).parents('tr').first(); 
    if (productId) { 
      $('#summary').html('Deleting <strong>' + name + '</strong>...'); 
      $.ajax({ 
        url: productsServiceUrl + '/' + productId, 
        type: 'DELETE', 
        dataType: 'json', 
        success: function () { 
          row.remove(); 
          $('#summary').html('<strong>' + name + '</strong> successfully deleted.'); 
        }, 
        error: function (xhr, textStatus, error) { 
          var text = error + (error.request ? ' - ' + error.request.status : ''); 
          $('#errorlog').append($('<li>').text(text)); 
        } 
      }); 
      event.preventDefault(); 
    } 
  }); 
 
  // Handle refresh 
  $(document.body).on('click', '.button-refresh', function (event) { 
    refreshProducts(); 
    event.preventDefault(); 
  }); 
   
  // Handle repository change 
  $('#repository').on('change', function () { 
    var valueSelected = this.value; 
     
    if (valueSelected) { 
      $.ajax({ 
        url: repositoryServiceUrl + '/' + valueSelected, 
        type: 'GET', 
        success: function () { 
          $('#summary').html('The current repository is now <strong>' + valueSelected + '</strong>.'); 
          refreshProducts(); 
        }, 
        error: function (xhr, textStatus, error) { 
          var text = error + (error.request ? ' - ' + error.request.status : ''); 
          $('#errorlog').append($('<li>').text(text)); 
        } 
      }); 
    } 
  }); 
   
  // Get current repository 
  function getRepository() { 
    $.ajax({ 
      url: repositoryServiceUrl, 
      type: 'GET', 
      success: function (data) { 
        if (data == 'Local') { 
          $('#repository').val('Local'); 
        } else { 
          $('#repository').val('ServiceBus'); 
        } 
      }, 
      error: function (xhr, textStatus, error) { 
        var text = error + (error.request ? ' - ' + error.request.status : ''); 
        $('#errorlog').append($('<li>').text(text)); 
      } 
    }); 
  } 
 
  // On initial load, start by fetching the current data 
  getRepository(); 
  refreshProducts(); 
});

ASP.NET Web API

The ASP.NET MVC 4 application implements the following classes:
  • ProductsController: this service can be used to access products data.
  • RepositoryController: this service can be used to select the data repository used by the ProductRepository class.
  • ProductRepository: this class defines the data access layer (DAL) used by the ProductsController to access data from the in-process cache or the remote database via Service Bus.
  • SimpleContainer: simplifies service location and dependency resolution.
The following table contains the code of the ProductsController.cs class.
#region Using Directives
using System;
using System.Web.Http;
#endregion

namespace ProductStore.Controllers 
{ 
  public class ProductsController : ApiController 
  { 
    #region Private Constants 
    private const string NotificationHubNameAppSetting = "NotificationHubName"; 
    private const string NotificationHubConnectionStringAppSetting = "NotificationHubConnectionString"; 
    #endregion 
 
    #region Private Fields 
    private readonly IProductRepository repository; 
    #endregion 
 
    #region Private Static Fields 
    private static readonly string notificationHubName; 
    private static readonly string notificationHubConnectionString; 
    #endregion 
 
    #region Static Constructor 
    static ProductsController() 
    { 
      var reader = new AppSettingsReader(); 
      notificationHubName =  
        reader.GetValue(NotificationHubNameAppSetting, typeof(string)) as string; 
      notificationHubConnectionString =  
        reader.GetValue(NotificationHubConnectionStringAppSetting, typeof(string)) as string; 
    } 
    #endregion 
 
    #region Public Constructor 
    public ProductsController(IProductRepository repository) 
    { 
      if (repository == null) 
      { 
        throw new ArgumentNullException("repository"); 
      } 
      this.repository = repository; 
    }  
    #endregion 
 
    #region Public Methods 
    [HttpGet] 
    public IEnumerable<Product> RetreiveAllProducts() 
    { 
      try 
      { 
        return repository.GetProducts(); 
      } 
      catch (Exception ex) 
      { 
        var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest) 
          { 
            Content = new StringContent(ex.Message), 
            ReasonPhrase = "An error occurred while retrieving products from the repository." 
          }; 
        throw new HttpResponseException(httpResponseMessage); 
      } 
    } 
 
    public Product GetProduct(int id) 
    { 
      try 
      { 
        var item = repository.GetProduct(id); 
        if (item == null) 
        { 
          var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.NotFound) 
            { 
              Content = new StringContent(string.Format("No product with ID = {0} was found.", id)), 
              ReasonPhrase = "Product ID Not Found" 
            }; 
          throw new HttpResponseException(httpResponseMessage); 
        } 
        return item; 
      } 
      catch (Exception ex) 
      { 
        var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.NotFound) 
        { 
          Content = new StringContent(ex.Message), 
          ReasonPhrase = "An error occurred while retrieving the product from the repository." 
        }; 
        throw new HttpResponseException(httpResponseMessage); 
      } 
    } 
 
    public IEnumerable<Product> GetProductByCategory(string category) 
    { 
      try 
      { 
        return repository.GetProductsByCategory(category); 
      } 
      catch (Exception ex) 
      { 
        var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest) 
        { 
          Content = new StringContent(ex.Message), 
          ReasonPhrase = "An error occurred while retrieving products from the repository." 
        }; 
        throw new HttpResponseException(httpResponseMessage); 
      } 
    } 
 
    public HttpResponseMessage PostProduct(Product item) 
    { 
      try 
      { 
        item = repository.AddProduct(item); 
        var response = Request.CreateResponse(HttpStatusCode.Created, item); 
 
        var uri = Url.Link("DefaultApi", new { id = item.ProductId }); 
        if (uri != null) 
        { 
          response.Headers.Location = new Uri(uri); 
        } 
        SendNotification("Products", string.Format("{0} is now available", item.Name),  
                                                   "productservice"); 
        return response; 
      } 
      catch (Exception ex) 
      { 
        var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest) 
        { 
          Content = new StringContent(ex.Message), 
          ReasonPhrase = "An error occurred while inserting the product into the repository." 
        }; 
        throw new HttpResponseException(httpResponseMessage); 
      } 
    } 
 
    public void PutProduct(int id, Product item) 
    { 
      try 
      { 
        item.ProductId = id; 
        if (!repository.UpdateProduct(item)) 
        { 
          throw new HttpResponseException(HttpStatusCode.NotFound); 
        } 
      } 
      catch (Exception ex) 
      { 
        var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest) 
        { 
          Content = new StringContent(ex.Message), 
          ReasonPhrase = "An error occurred while updating the product on the repository." 
        }; 
        throw new HttpResponseException(httpResponseMessage); 
      } 
    } 
 
    public void DeleteProduct(int id) 
    { 
      try 
      { 
        if (!repository.DeleteProduct(id)) 
        { 
          throw new HttpResponseException(HttpStatusCode.NotFound); 
        } 
      } 
      catch (Exception ex) 
      { 
        var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.BadRequest) 
        { 
          Content = new StringContent(ex.Message), 
          ReasonPhrase = "An error occurred while deleting the product from the repository." 
        }; 
        throw new HttpResponseException(httpResponseMessage); 
      } 
    } 
    #endregion 
 
    #region Private Methods 
    private void SendNotification(string title, string message, string tag) 
    { 
      try 
      { 
        if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(message)) 
        { 
          return; 
        } 
        var hub = NotificationHubClient.CreateClientFromConnectionString(notificationHubConnectionString, 
                                         notificationHubName); 
        var notification = string.IsNullOrWhiteSpace(tag) ? 
                   new TemplateNotification(new Dictionary<string, string> {{"title", title},  
                                                                            {"msg", message}}) : 
                   new TemplateNotification(new Dictionary<string, string> {{"title", title},  
                                                                            {"msg", message}}, tag); 
        hub.SendNotificationAsync(notification); 
      } 
       // ReSharper disable EmptyGeneralCatchClause 
      catch 
      // ReSharper restore EmptyGeneralCatchClause 
      { 
      } 
    } 
    #endregion 
  } 
}
The following table contains the code of the ProductRepository.cs class.
#region Using Directives 
using System; 
using System.Collections.Generic; 
using System.Configuration; 
using System.Linq; 
using System.ServiceModel; 
using Contracts; 
using ProductStore.Controllers;  
#endregion 
 
namespace ProductStore.Models 
{ 
  public class ProductRepository : IProductRepository 
  { 
    #region Private Constants 
    private const string LocalRepository = "Local"; 
    private const string ClientEndpointAppSetting = "ClientEndpoint"; 
    private const string DefaultClientEndpoint = "netTcpRelayBinding"; 
    #endregion 
 
    #region Private Static Fields 
    private static readonly string clientEndpoint; 
    #endregion 
 
    #region Private Instance Fields 
    private readonly List<Product> products = new List<Product>(); 
    private ChannelFactory<IProductService> factory; 
    private int nextId = 1; 
    #endregion 
 
    #region Static Constructor 
    static ProductRepository() 
    { 
      var reader = new AppSettingsReader(); 
      clientEndpoint = reader.GetValue(ClientEndpointAppSetting, typeof(string)) as string; 
      if (string.IsNullOrWhiteSpace(clientEndpoint)) 
      { 
        clientEndpoint = DefaultClientEndpoint; 
      } 
    } 
    #endregion 
 
    #region Public Constructor 
    public ProductRepository() 
    { 
      AddProduct(new Product { Name = "Spaghetti", Category = "Groceries", Price = 1.39M }); 
      AddProduct(new Product { Name = "Babo", Category = "Toys", Price = 3.75M }); 
      AddProduct(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M }); 
      AddProduct(new Product { Name = "Grand Theft Auto 5", Category = "Video Games", Price = 59.99M }); 
    }  
    #endregion 
 
    #region Public Methods 
    public IEnumerable<Product> GetProducts() 
    { 
      if (string.Compare(RepositoryController.Repository, LocalRepository,  
                         StringComparison.InvariantCultureIgnoreCase) == 0) 
      { 
        return products; 
      } 
      try 
      { 
        var channel = GetChannel(); 
        return channel.GetProducts(); 
      } 
      catch (Exception) 
      { 
        HandleException(); 
        throw; 
      } 
    } 
 
    public IEnumerable<Product> GetProductsByCategory(string category) 
    { 
      if (string.Compare(RepositoryController.Repository, LocalRepository,  
                         StringComparison.InvariantCultureIgnoreCase) == 0) 
      { 
        return products.Where(p => string.Equals(p.Category, category,  
                                                 StringComparison.OrdinalIgnoreCase));  
      } 
      try 
      { 
        var channel = GetChannel(); 
        return channel.GetProductsByCategory(category); 
      } 
      catch (Exception) 
      { 
        HandleException(); 
        throw; 
      } 
    } 
 
    public Product GetProduct(int id) 
    { 
      if (string.Compare(RepositoryController.Repository, LocalRepository,  
                         StringComparison.InvariantCultureIgnoreCase) == 0) 
      { 
        return products.Find(p => p.ProductId == id); 
      } 
      try 
      { 
        var channel = GetChannel(); 
        return channel.GetProduct(id); 
      } 
      catch (Exception) 
      { 
        HandleException(); 
        throw; 
      } 
    } 
 
    public Product AddProduct(Product item) 
    { 
      if (item == null) 
      { 
        throw new ArgumentNullException("item"); 
      } 
      if (string.Compare(RepositoryController.Repository, LocalRepository,  
                         StringComparison.InvariantCultureIgnoreCase) == 0) 
      { 
 
        item.ProductId = nextId++; 
        products.Add(item); 
        return item; 
      } 
      try 
      { 
        var channel = GetChannel(); 
        return channel.AddProduct(item); 
      } 
      catch (Exception) 
      { 
        HandleException(); 
        throw; 
      } 
    } 
 
    public bool DeleteProduct(int id) 
    { 
      if (string.Compare(RepositoryController.Repository, LocalRepository,  
                         StringComparison.InvariantCultureIgnoreCase) == 0) 
      { 
        if (products.Find(p => p.ProductId == id) != null) 
        { 
          products.RemoveAll(p => p.ProductId == id); 
          return true; 
        } 
        return false; 
      } 
      try 
      { 
        var channel = GetChannel(); 
        channel.DeleteProduct(id); 
        return true; 
      } 
      catch (Exception) 
      { 
        HandleException(); 
        throw; 
      } 
    } 
 
    public bool UpdateProduct(Product item) 
    { 
      if (item == null) 
      { 
        throw new ArgumentNullException("item"); 
      } 
      if (string.Compare(RepositoryController.Repository, LocalRepository,  
                         StringComparison.InvariantCultureIgnoreCase) == 0) 
      { 
        var index = products.FindIndex(p => p.ProductId == item.ProductId); 
        if (index == -1) 
        { 
          return false; 
        } 
        products.RemoveAt(index); 
        products.Add(item); 
        return true; 
      } 
      try 
      { 
        var channel = GetChannel(); 
        channel.UpdateProduct(item); 
        return true; 
      } 
      catch (Exception) 
      { 
        HandleException(); 
        throw; 
      } 
    }  
    #endregion 
 
    #region Private Methods 
    private IProductService GetChannel() 
    { 
      lock (this) 
      { 
        if (factory == null) 
        { 
          factory = new ChannelFactory<IProductService>(clientEndpoint); 
        } 
      } 
      return factory.CreateChannel(); 
    } 
 
    private void HandleException() 
    { 
      lock (this) 
      { 
        if (factory == null) 
        { 
          return; 
        } 
        factory.Abort(); 
        factory = null; 
      } 
    } 

    #endregion 
  } 
}
The following table contains an excerpt from the configuration file of the web application. In particular, the system.serviceModel section defines the 3 client endpoints that can be used by the ProductRepository class to invoke the Service Bus Relay Service. The ClientEndpoint app settings specifies the name of the client endpoint used by the ProductRepository class.
<?xml version="1.0" encoding="utf-8"?> 
<!-- 
 For more information on how to configure your ASP.NET application, please visit 
 http://go.microsoft.com/fwlink/?LinkId=169433 
 --> 
<configuration> 
  ... 
  <appSettings> 
      <!-- Service Bus specific app setings for messaging connections --><add key="webpages:Version" value="2.0.0.0" /> 
      <add key="webpages:Enabled" value="false" /> 
      <add key="PreserveLoginUrl" value="true" /> 
      <add key="ClientValidationEnabled" value="true" /> 
      <add key="UnobtrusiveJavaScriptEnabled" value="true" /> 
      <add key="ClientEndpoint" value="netTcpRelayBinding" /> 
      <add key="NotificationHubName" value="[YOUR-NOTIFICATION-HUB-NAME]" /> 
      <add key="NotificationHubConnectionString" value="[YOUR-NOTIFICATION-HUB-CONNECTION-STRING]" /> 
  </appSettings> 
  ... 
  <system.serviceModel> 
  <!-- 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 Configuration --> 
  <client> 
    <endpoint address="sb://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/nettcp"  
              behaviorConfiguration="serviceBusEndpointBehavior"  
              binding="netTcpRelayBinding"  
              bindingConfiguration="netTcpRelayBinding"  
              contract="Contracts.IProductService"  
              name="netTcpRelayBinding" /> 
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/basichttp"  
              behaviorConfiguration="serviceBusEndpointBehavior"  
              binding="basicHttpRelayBinding"  
              bindingConfiguration="basicHttpRelayBinding"  
              contract="Contracts.IProductService"  
              name="basicHttpRelayBinding" /> 
    <endpoint address="https://[YOUR-SERVICE-BUS-NAMESPACE].servicebus.windows.net/products/webhttp"  
              behaviorConfiguration="webHttpEndpointBehavior"  
              binding="webHttpRelayBinding"  
              bindingConfiguration="webHttpRelayBinding"  
              contract="Contracts.IProductService"  
              name="webHttpRelayBinding" /> 
  </client> 
   
  <extensions> 
    <!-- In this extension section we are introducing all known service bus extensions. User can remove the ones they don't need. --> 
    <behaviorExtensions> 
    <add name="connectionStatusBehavior"  
         type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="transportClientEndpointBehavior"  
         type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="serviceRegistrySettings" type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    </behaviorExtensions> 
    <bindingElementExtensions> 
    <add name="netMessagingTransport"  
         type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="tcpRelayTransport"  
         type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="httpRelayTransport"  
         type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="httpsRelayTransport"  
         type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="onewayRelayTransport"  
    type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    </bindingElementExtensions> 
    <bindingExtensions> 
    <add name="basicHttpRelayBinding"  
         type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="webHttpRelayBinding"  
         type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="ws2007HttpRelayBinding"  
         type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="netTcpRelayBinding"  
         type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="netOnewayRelayBinding"  
         type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="netEventRelayBinding"  
         type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    <add name="netMessagingBinding"  
    type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    </bindingExtensions> 
  </extensions></system.serviceModel> 
</configuration>

NOTE: make sure to replace the following placeholders in the Web.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.
  • [YOUR-NOTIFICATION-HUB-NAME]: the name of the notification hub used to send push notifications.
  • [YOUR-NOTIFICATION-HUB-CONNECTION-STRING]: the DefaultFullSharedAccessSignature connection string of the notification hub used to send push notifications.  

Create a web site in the portal

  1. Login to the Windows Azure Management Portal.
  2. Click the New icon on the bottom left of the dashboard.

    Create New
  3. Click the Web Site icon, and click Quick Create.

  4. Select a Subscription, a Region and enter a URL for the new web site, then click Create Web Site.
  5. Select the new web site on the Windows Azure Management Portal and click the Dashboard tab. Click the Download the publish profile link and save the .PublishSettings file on your disk. For more information, see How to deploy an ASP.NET web application to a Windows Azure Web Site using Visual Studio
  6. Open your solution in Visual Studio, right click the HTML5 web site and click Publish Web Site in the context menu.

     
  7. Click the Import button in the the Publish Web dialog.
     
  8. Click the Browse button and select the .PublishSettings file obtained at step 5.
     
  9. Publishing data are automatically loaded from the .PublishSettings file. Click the Next button


      
  10. Click the Next button.
     
  11. Finally, click the Publish button.

Windows Store App

The following picture shows the Windows Store App that can be used to test the ASP.NET Web API RESTful service.
 
 
The following table contains the code of the App.xaml.cs class that contains the client-side logic used to invoke the ASP.NET Web API RESTful service. The App class performs the following actions:
#region Using Directives 
using System; 
using System.Net.Http; 
using System.Net.Http.Headers; 
using System.Threading.Tasks; 
using Microsoft.WindowsAzure.Messaging; 
using Windows.ApplicationModel; 
using Windows.ApplicationModel.Activation; 
using Windows.Data.Xml.Dom; 
using Windows.Networking.PushNotifications; 
using Windows.UI.Notifications; 
using Windows.UI.Popups; 
using Windows.UI.Xaml; 
using Windows.UI.Xaml.Controls;  
#endregion 
 
namespace WindowsStoreApp 
{ 
  /// <summary> 
  /// Provides application-specific behavior to supplement the default Application class. 
  /// </summary> 
  sealed partial class App 
  { 
    #region Private Constants 
    private const string NotificationHubName = "[YOUR-NOTIFICATION-HUB-NAME]"; 
    private const string ConnectionString = "[YOUR-NOTIFICATION-HUB-CONNECTION-STRING]"; 
    #endregion 
 
    #region Public Static Properties 
    public static string ServiceUrl = "http://[YOUR-WEB-SITE-NAME].azurewebsites.net/"; 
    public static HttpClient HttpClient; 
    #endregion 
 
    #region Private Instance Fields 
    // Notification Hub 
    readonly NotificationHub notificationHub; 
    #endregion 
 
    #region Public Constructor 
    /// <summary> 
    /// Initializes the singleton application object. This is the first line of authored code 
    /// executed, and as such is the logical equivalent of main() or WinMain(). 
    /// </summary> 
    public App() 
    { 
      // Create HttpClient object 
      HttpClient = new HttpClient { BaseAddress = new Uri(ServiceUrl) }; 
      HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 
      notificationHub = new NotificationHub(NotificationHubName, 
                                            ConnectionString); 
      InitializeComponent(); 
      Suspending += OnSuspending; 
    }  
    #endregion 
 
    /// <summary> 
    /// It takes care of refreshing any existing registrations and reacquiring 
    /// and re-associating the WNS channel URI whenever needed. 
    /// It also checks whether a nativeRegistration exists for a 
    /// Toast notification and creates a new nativeRegistration if it's not found. 
    /// </summary> 
    /// <returns>A Task</returns> 
    async Task InitializeNotificationsAsync() 
    { 
      string message = null; 
      try 
      { 
        var channel =  
           await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync(); 
        await notificationHub.RegisterTemplateAsync(channel.Uri,  
                                                    BuildTextToastTemplate(),  
                                                    "toastRegistration",  
                                                    new[] { "productservice" }); 
        await notificationHub.RegisterTemplateAsync(channel.Uri,  
                                                    BuildTextTileTemplate(),  
                                                    "tileRegistration",  
                                                    new[] { "productservice" }); 
      } 
      catch (Exception ex) 
      { 
        message = ex.Message; 
      } 
 
      if (string.IsNullOrWhiteSpace(message)) 
      { 
        return; 
      } 
      var messageDialog = new MessageDialog(message, "Notification Hub"); 
      await messageDialog.ShowAsync(); 
    } 
 
    /// <summary>; 
    /// It is used when calling CreatingTemplateRegistrationForApplicationAsync 
    /// on the notificationHub instance. 
    /// It uses the Windows’ native notification template library. 
    /// In the ToastText01 template, modify its text node 
    /// by adding a Notification Hub template expression: $(msg) 
    /// </summary> 
    /// <returns>The xml document containing the toast template.</returns> 
    static string BuildTextToastTemplate() 
    { 
      var template = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02); 
      var textNode = template.SelectSingleNode("//text[@id='1']") as XmlElement; 
      if (textNode != null) 
      { 
        textNode.InnerText = "$(title)"; 
      } 
      textNode = template.SelectSingleNode("//text[@id='2']") as XmlElement; 
      if (textNode != null) 
      { 
        textNode.InnerText = "$(msg)"; 
      } 
      textNode = template.SelectSingleNode("//toast") as XmlElement; 
      if (textNode != null) 
      { 
        textNode.SetAttribute("launch", "$(msg)"); 
      } 
      return template.DocumentElement.GetXml(); 
    } 
 
    /// <summary>; 
    /// It is used when calling CreatingTemplateRegistrationForApplicationAsync 
    /// on the notificationHub instance. 
    /// It uses the Windows’ native notification template library. 
    /// In the ToastText01 template, modify its text node 
    /// by adding a Notification Hub template expression: $(msg) 
    /// </summary> 
    /// <returns>The xml document containing the toast template.</returns> 
    static string BuildTextTileTemplate() 
    { 
      var template = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWideText09); 
      var textNode = template.SelectSingleNode("//text[@id='1']") as XmlElement; 
      if (textNode != null) 
      { 
        textNode.InnerText = "$(title)"; 
      } 
      textNode = template.SelectSingleNode("//text[@id='2']") as XmlElement; 
      if (textNode != null) 
      { 
        textNode.InnerText = "$(msg)"; 
      } 
      return template.DocumentElement.GetXml(); 
    } 
 
    /// <summary> 
    /// Invoked when the application is launched normally by the end user. Other entry points 
    /// will be used when the application is launched to open a specific file, to display 
    /// search results, and so forth. 
    /// </summary> 
    /// <param name="args">Details about the launch request and process.</param> 
    protected async override void OnLaunched(LaunchActivatedEventArgs args) 
    { 
      await InitializeNotificationsAsync(); 
      // Do not repeat app initialization when already running, just ensure that 
      // the window is active 
      if (args.PreviousExecutionState == ApplicationExecutionState.Running) 
      { 
        Window.Current.Activate(); 
        return; 
      } 
 
      if (args.PreviousExecutionState == ApplicationExecutionState.Terminated) 
      { 
      } 
 
      // Create a Frame to act navigation context and navigate to the first page 
      var rootFrame = new Frame(); 
      if (!rootFrame.Navigate(typeof(MainPage))) 
      { 
        throw new Exception("Failed to create initial page"); 
      } 
 
      // Place the frame in the current Window and ensure that it is active 
      Window.Current.Content = rootFrame; 
      Window.Current.Activate(); 
    } 
 
    /// <summary> 
    /// Invoked when application execution is being suspended. Application state is saved 
    /// without knowing whether the application will be terminated or resumed with the contents 
    /// of memory still intact. 
    /// </summary> 
    /// <param name="sender">The source of the suspend request.</param> 
    /// <param name="e">Details about the suspend request.</param> 
    private void OnSuspending(object sender, SuspendingEventArgs e) 
    { 
      var deferral = e.SuspendingOperation.GetDeferral(); 
      deferral.Complete(); 
    } 
  } 
}
NOTE: make sure to replace the following placeholders in the App.xaml.cs file:
  • [YOUR-WEB-SITE-NAME]: specify the name of the Windows Azure Web Site hosting the ASP.NET Web API RESTful service.
  • [YOUR-NOTIFICATION-HUB-NAME]: specify the name of the notification hub used to send push notifications.
  • [YOUR-NOTIFICATION-HUB-CONNECTION-STRING]: specify the DefaultFullSharedAccessSignature connection string of the notification hub used to send push notifications. 
The following table contains the code of the MainPage.xaml.cs class that contains the client-side logic used to invoke the ASP.NET Web API RESTful service, and the code of the Product class that defines entity model. The class uses the HttpClient to communicate with the RESTful service.
#region Using Directives
using System.Collections.ObjectModel;
using System.Net.Http;
using System.Text;
using Newtonsoft.Json;
using System;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; 
#endregion

namespace WindowsStoreApp 
{ 
  public class Product 
  { 
    [JsonProperty(PropertyName = "productId", Order = 1)] 
    public int ProductId { get; set; } 
    [JsonProperty(PropertyName = "name", Order = 2)] 
    public string Name { get; set; } 
    [JsonProperty(PropertyName = "category", Order = 3)] 
    public string Category { get; set; } 
    [JsonProperty(PropertyName = "price", Order = 4)] 
    public float Price { get; set; } 
  } 
 
  public sealed partial class MainPage 
  { 
    #region Private Constants 
    private const string ProductUrl = "api/products/{0}"; 
    private const string ProductsUrl = "api/products";  
    #endregion 
 
    #region Private Fields 
    private ObservableCollection<Product> products; 
    #endregion 
 
    #region Public Constructor 
    public MainPage() 
    { 
      InitializeComponent(); 
    }  
    #endregion 
 
    #region Private Methods 
    private async void InsertProduct(Product product) 
    { 
      Exception exception = null; 
      try 
      { 
        Summary.Text = string.Format("Adding {0}...", product.Name); 
        var json = JsonConvert.SerializeObject(product); 
        var httpResponseMessage = await App.HttpClient.PostAsync(ProductUrl, 
                                     new StringContent(json, 
                                               Encoding.UTF8, 
                                               "application/json")); 
        if (httpResponseMessage == null || 
          !httpResponseMessage.IsSuccessStatusCode) 
        { 
          return; 
        } 
        products.Add(product); 
        Summary.Text = string.Format("{0} successfully added.", product.Name); 
        ProductName.Text = string.Empty; 
        ProductCategory.Text = string.Empty; 
        ProductPrice.Text = string.Empty; 
      } 
      catch (Exception ex) 
      { 
        Summary.Text = ex.Message; 
        exception = ex; 
      } 
 
      if (exception != null) 
      { 
        await new MessageDialog(exception.Message, "Error loading products").ShowAsync(); 
      } 
    } 
 
    private async void RefreshProducts() 
    { 
      Exception exception = null; 
      try 
      { 
        Summary.Text = "Retrieving products..."; 
        // This code refreshes the entries in the list view by querying the Products table. 
        // The query excludes completed Products 
        var httpResponseMessage = await App.HttpClient.GetAsync(ProductsUrl); 
        if (httpResponseMessage == null || 
          !httpResponseMessage.IsSuccessStatusCode) 
        { 
          return; 
        } 
        var payload = await httpResponseMessage.Content.ReadAsStringAsync(); 
        products = JsonConvert.DeserializeObject<ObservableCollection<Product>>(payload); 
        Summary.Text = products == null || products.Count == 0 ? 
                 "No product retrieved" : 
                 string.Format("{0} product(s) successfully retrieved.", 
                 products.Count); 
      } 
      catch (Exception ex) 
      { 
        Summary.Text = ex.Message; 
        exception = ex; 
      } 
 
      if (exception != null) 
      { 
        await new MessageDialog(exception.Message, "Error loading products").ShowAsync(); 
      } 
      else 
      { 
        ListProducts.ItemsSource = products; 
      } 
    } 
 
    private void ButtonRefresh_Click(object sender, RoutedEventArgs e) 
    { 
      RefreshProducts(); 
    } 
 
    private async void UpdateProduct_OnClick(object sender, RoutedEventArgs e) 
    { 
      var stackPanel = (StackPanel)((Button)sender).Parent; 
      if (stackPanel == null || !(stackPanel.DataContext is Product)) 
      { 
        return; 
      } 
      var product = stackPanel.DataContext as Product; 
      if (string.IsNullOrWhiteSpace(product.Name)) 
      { 
        Summary.Text = "The name cannot be null"; 
        return; 
      } 
      if (string.IsNullOrWhiteSpace(product.Category)) 
      { 
        Summary.Text = "The category cannot be null"; 
        return; 
      } 
      Exception exception = null; 
      try 
      { 
        Summary.Text = string.Format("Updating {0}...", product.Name); 
        var json = JsonConvert.SerializeObject(product); 
        var httpResponseMessage =  
                await App.HttpClient.PutAsync(string.Format(ProductUrl,  
                                                            product.ProductId), 
                                              new StringContent(json, 
                                                                Encoding.UTF8, 
                                                                "application/json")); 
        if (httpResponseMessage == null || 
          !httpResponseMessage.IsSuccessStatusCode) 
        { 
          return; 
        } 
        Summary.Text = string.Format("{0} successfully updated.", product.Name); 
      } 
      catch (Exception ex) 
      { 
        Summary.Text = ex.Message; 
        exception = ex; 
      } 
 
      if (exception != null) 
      { 
        await new MessageDialog(exception.Message, "Error updating product").ShowAsync(); 
      } 
    } 
 
    private async void DeleteProduct_OnClick(object sender, RoutedEventArgs e) 
    { 
      var stackPanel = (StackPanel)((Button)sender).Parent; 
      if (stackPanel == null || !(stackPanel.DataContext is Product)) 
      { 
        return; 
      } 
      var product = stackPanel.DataContext as Product; 
      Exception exception = null; 
      try 
      { 
        Summary.Text = string.Format("Deleting {0}...", product.Name); 
        var httpResponseMessage =  
                  await App.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Delete,  
                                                                        string.Format(ProductUrl,  
                                                                                      product.ProductId))); 
        if (httpResponseMessage == null || 
          !httpResponseMessage.IsSuccessStatusCode) 
        { 
          return; 
        } 
        Summary.Text = string.Format("{0} successfully deleted.", product.Name); 
        products.Remove(product); 
      } 
      catch (Exception ex) 
      { 
        Summary.Text = ex.Message; 
        exception = ex; 
      } 
 
      if (exception != null) 
      { 
        await new MessageDialog(exception.Message, "Error deleting product").ShowAsync(); 
      } 
    } 
 
    private void ButtonSave_Click(object sender, RoutedEventArgs e) 
    { 
      float price; 
      if (string.IsNullOrWhiteSpace(ProductName.Text)) 
      { 
        Summary.Text = "The name cannot be null"; 
        return; 
      } 
      if (string.IsNullOrWhiteSpace(ProductCategory.Text)) 
      { 
        Summary.Text = "The category cannot be null"; 
        return; 
      } 
      if (!float.TryParse(ProductPrice.Text, out price)) 
      { 
        Summary.Text = "The price must be a number"; 
        return; 
      } 
      var product = new Product { Name = ProductName.Text, Category = ProductCategory.Text, Price = price }; 
      InsertProduct(product); 
    } 
 
    protected override void OnNavigatedTo(NavigationEventArgs e) 
    { 
      RefreshProducts(); 
    }  
    #endregion 
  } 
}

When a new item is added to the ProductDb, the ASP.NET Web API RESTful service sends a notification. The Windows Phone 8 app registers two templates with the notification hub, one to receive toast notification and one to receive live tiles, as shown in the picture below.

Windows Phone 8 App

The following picture shows the Windows Phone 8 App that can be used to test the ASP.NET Web Api RESTful service.
The following table contains the code of the App.xaml.cs class that contains the client-side logic used to invoke the ASP.NET Web API RESTful service. The App class creates the HttpClient object used by the application pages to communicate with the RESTful service.
#region Using Directives 
using System; 
using System.Diagnostics; 
using System.Net.Http; 
using System.Net.Http.Headers; 
using System.Windows; 
using System.Windows.Markup; 
using System.Windows.Navigation; 
using Microsoft.Phone.Controls; 
using Microsoft.Phone.Shell; 
using ProductWindowsPhoneApp.Resources; 
#endregion 
 
namespace ProductWindowsPhoneApp 
{ 
  public partial class App 
  { 
    #region Public Static Properties 
    public static PhoneApplicationFrame RootFrame { get; private set; } 
    public static string ServiceUrl = "http://[YOUR-WEB-SITE-NAME].azurewebsites.net/"; 
    public static HttpClient HttpClient; 
    #endregion 
     
    /// <summary> 
    /// Constructor for the Application object. 
    /// </summary> 
    public App() 
    { 
      // Create HttpClient object 
      HttpClient = new HttpClient { BaseAddress = new Uri(ServiceUrl) }; 
      HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 
 
      // Global handler for uncaught exceptions. 
      UnhandledException += Application_UnhandledException; 
 
      // Standard XAML initialization 
      InitializeComponent(); 
 
      // Phone-specific initialization 
      InitializePhoneApplication(); 
 
      // Language display initialization 
      InitializeLanguage(); 
 
      // Show graphics profiling information while debugging. 
      if (Debugger.IsAttached) 
      { 
        // Display the current frame rate counters. 
        Application.Current.Host.Settings.EnableFrameRateCounter = true; 
 
        // Show the areas of the app that are being redrawn in each frame. 
        //Application.Current.Host.Settings.EnableRedrawRegions = true; 
 
        // Enable non-production analysis visualization mode, 
        // which shows areas of a page that are handed off to GPU with a colored overlay. 
        //Application.Current.Host.Settings.EnableCacheVisualization = true; 
 
        // Prevent the screen from turning off while under the debugger by disabling 
        // the application's idle detection. 
        // Caution:- Use this under debug mode only. Application that disables user 
        // idle detection will continue to run 
        // and consume battery power when the user is not using the phone. 
        PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled; 
      } 
 
    } 
 
    // Code to execute when the application is launching (eg, from Start) 
    // This code will not execute when the application is reactivated 
    private void Application_Launching(object sender, LaunchingEventArgs e) 
    { 
    } 
 
    // Code to execute when the application is activated (brought to foreground) 
    // This code will not execute when the application is first launched 
    private void Application_Activated(object sender, ActivatedEventArgs e) 
    { 
    } 
 
    // Code to execute when the application is deactivated (sent to background) 
    // This code will not execute when the application is closing 
    private void Application_Deactivated(object sender, DeactivatedEventArgs e) 
    { 
    } 
 
    // Code to execute when the application is closing (eg, user hit Back) 
    // This code will not execute when the application is deactivated 
    private void Application_Closing(object sender, ClosingEventArgs e) 
    { 
    } 
 
    // Code to execute if a navigation fails 
    private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) 
    { 
      if (Debugger.IsAttached) 
      { 
        // A navigation has failed; break into the debugger 
        Debugger.Break(); 
      } 
    } 
 
    // Code to execute on Unhandled Exceptions 
    private void Application_UnhandledException(object sender,  
                                                ApplicationUnhandledExceptionEventArgs e) 
    { 
      if (Debugger.IsAttached) 
      { 
        // An unhandled exception has occurred; break into the debugger 
        Debugger.Break(); 
      } 
    } 
 
    #region Phone application initialization 
 
    // Avoid double-initialization 
    private bool phoneApplicationInitialized = false; 
 
    // Do not add any additional code to this method 
    private void InitializePhoneApplication() 
    { 
      if (phoneApplicationInitialized) 
        return; 
 
      // Create the frame but don't set it as RootVisual yet; this allows the splash 
      // screen to remain active until the application is ready to render. 
      RootFrame = new TransitionFrame(); 
      RootFrame.Navigated += CompleteInitializePhoneApplication; 
 
      // Handle navigation failures 
      RootFrame.NavigationFailed += RootFrame_NavigationFailed; 
 
      // Handle reset requests for clearing the backstack 
      RootFrame.Navigated += CheckForResetNavigation; 
 
      // Ensure we don't initialize again 
      phoneApplicationInitialized = true; 
    } 
 
    // Do not add any additional code to this method 
    private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 
    { 
      // Set the root visual to allow the application to render 
      if (RootVisual != RootFrame) 
        RootVisual = RootFrame; 
 
      // Remove this handler since it is no longer needed 
      RootFrame.Navigated -= CompleteInitializePhoneApplication; 
    } 
 
    private void CheckForResetNavigation(object sender, NavigationEventArgs e) 
    { 
      // If the app has received a 'reset' navigation, then we need to check 
      // on the next navigation to see if the page stack should be reset 
      if (e.NavigationMode == NavigationMode.Reset) 
        RootFrame.Navigated += ClearBackStackAfterReset; 
    } 
 
    private void ClearBackStackAfterReset(object sender, NavigationEventArgs e) 
    { 
      // Unregister the event so it doesn't get called again 
      RootFrame.Navigated -= ClearBackStackAfterReset; 
 
      // Only clear the stack for 'new' (forward) and 'refresh' navigations 
      if (e.NavigationMode != NavigationMode.New && e.NavigationMode != NavigationMode.Refresh) 
        return; 
 
      // For UI consistency, clear the entire page stack 
      while (RootFrame.RemoveBackEntry() != null) 
      { 
        ; // do nothing 
      } 
    } 
 
    #endregion 
 
    // Initialize the app's font and flow direction as defined in its 
    // localized resource strings. 
    // To ensure that the font of your application is aligned 
    // with its supported languages and that the 
    // FlowDirection for each of those languages follows its 
    // traditional direction, ResourceLanguage 
    // and ResourceFlowDirection should be initialized in each 
    // resx file to match these values with that 
    // file's culture. For example: 
    // 
    // AppResources.es-ES.resx 
    // ResourceLanguage's value should be "es-ES" 
    // ResourceFlowDirection's value should be "LeftToRight" 
    // 
    // AppResources.ar-SA.resx 
    // ResourceLanguage's value should be "ar-SA" 
    // ResourceFlowDirection's value should be "RightToLeft" 
    // 
    // For more info on localizing Windows Phone apps see 
    // http://go.microsoft.com/fwlink/?LinkId=262072. 
    // 
    private void InitializeLanguage() 
    { 
      try 
      { 
        // Set the font to match the display language defined by the 
        // ResourceLanguage resource string for each supported language. 
        // 
        // Fall back to the font of the neutral language if the Display 
        // language of the phone is not supported. 
        // 
        // If a compiler error is hit then ResourceLanguage is missing from 
        // the resource file. 
        RootFrame.Language = XmlLanguage.GetLanguage(AppResources.ResourceLanguage); 
 
        // Set the FlowDirection of all elements under the root frame based 
        // on the ResourceFlowDirection resource string for each 
        // supported language. 
        // 
        // If a compiler error is hit then ResourceFlowDirection is missing from 
        // the resource file. 
        FlowDirection flow = (FlowDirection)Enum.Parse(typeof(FlowDirection),  
                              AppResources.ResourceFlowDirection); 
        RootFrame.FlowDirection = flow; 
      } 
      catch 
      { 
        // If an exception is caught here it is most likely due to either 
        // ResourceLangauge not being correctly set to a supported language 
        // code or ResourceFlowDirection is set to a value other than LeftToRight 
        // or RightToLeft. 
 
        if (Debugger.IsAttached) 
        { 
          Debugger.Break(); 
        } 
 
        throw; 
      } 
    } 
  } 
}

NOTE: make sure to replace the following placeholders in the App.xaml.cs file:

  • [YOUR-WEB-SITE-NAME]: specify the name of the Windows Azure Web Site hosting the ASP.NET Web API RESTful service.

The following table contains the code of the MainPage.xaml.cs class that contains the client-side logic used to invoke the ASP.NET Web API RESTful service, and the code of the Product class that defines entity model. The class uses the HttpClient to communicate with the RESTful service. The MainPage class performs the following actions:

  • Creates and open a new HttpNotificationChannel object to receive toast notifications and live tiles from the Windows Phone Push Notification Service.  
  • Creates a NotificationHub object used to register a template for toast notifications and one for live tiles with the notification hub.
#region Using Directives 
using System; 
using System.Collections.ObjectModel; 
using System.Linq; 
using System.Text; 
using System.Windows.Navigation; 
using Microsoft.Phone.Notification; 
using Microsoft.WindowsAzure.Messaging; 
using System.Windows; 
using Newtonsoft.Json; 
#endregion 
 
namespace ProductWindowsPhoneApp 
{ 
  public partial class MainPage 
  { 
    #region Private Constants 
    // Private Constants 
    private const string NotificationHubName = "[YOUR-NOTIFICATION-HUB-NAME]"; 
    private const string ConnectionString = "[YOUR-NOTIFICATION-HUB-CONNECTION-STRING]"; 
    private const string ToastNotificationBodyTemplate = "<wp:Notification xmlns:wp=\"WPNotification\">" + 
                               "<wp:Toast>*" + 
                               "<wp:Text1>$(title)</wp:Text1>" + 
                               "<wp:Text2>$(msg)</wp:Text2>" + 
                               "<wp:Param></wp:Param>" + 
                               "</wp:Toast>" + 
                               "</wp:Notification>"; 
    private const string LiveTileBodyTemplate = "<wp:Notification xmlns:wp=\"WPNotification\" Version=\"2.0\">" + 
                          "<wp:Tile Template=\"FlipTile\">" + 
                          "<wp:Title>$(title)</wp:Title>" + 
                          "<wp:Count>1</wp:Count>" + 
                          "<wp:BackTitle>$(title)</wp:BackTitle>" + 
                          "<wp:BackContent>$(msg)</wp:BackContent>" + 
                          "<wp:BackgroundImage>Assets/MediumTile.png</wp:BackgroundImage>" + 
                          "</wp:Tile>" + 
                          "</wp:Notification>"; 
    private const string ToastNotificationTemplateName = "ToastNotification"; 
    private const string LiveTileTemplateName = "LiveTile"; 
    private const string GetAllProductsUrl = "api/products"; 
    private const string GetProductByIdUrl = "api/products/{0}"; 
    private const string GetProductsByCategoryUrl = "api/products?category={0}"; 
    #endregion 
 
    #region Private Fields 
    private bool registered; 
    #endregion 
 
    #region Public Static Properties 
    public static ObservableCollection<Product> Products; 
    public static string MessageText; 
    public static string Category; 
    public static string ProductId; 
    #endregion 
 
    #region Public Constructor 
    // Constructor 
    public MainPage() 
    { 
      InitializeComponent(); 
      Loaded += MainPage_Loaded; 
    }  
    #endregion 
 
    #region Protected Methods 
    protected override void OnNavigatedTo(NavigationEventArgs e) 
    { 
      if (string.IsNullOrWhiteSpace(MessageText)) 
      { 
        return; 
      } 
      Message.Text = MessageText; 
      MessageText = null; 
    } 
    #endregion 
     
    #region Private Methods 
    private async void GetAllProducts() 
    { 
      try 
      { 
        Message.Text = "Retrieving Products..."; 
        var httpResponseMessage = await App.HttpClient.GetAsync(GetAllProductsUrl); 
        if (httpResponseMessage != null && 
          httpResponseMessage.IsSuccessStatusCode) 
        { 
          var payload = await httpResponseMessage.Content.ReadAsStringAsync(); 
          Products = JsonConvert.DeserializeObject<ObservableCollection<Product>>(payload); 
        } 
        Message.Text = Products == null || Products.Count == 0 ?  
                 "No product retrieved" :  
                 string.Format("{0} product(s) successfully retrieved.",  
                 Products.Count); 
      } 
      catch (Exception ex) 
      { 
        Message.Text = ex.Message; 
      } 
      ProductList.ItemsSource = Products; 
    } 
 
    private async void GetProductById() 
    { 
      try 
      { 
        Message.Text = "Retrieving Products..."; 
        var httpResponseMessage = await App.HttpClient.GetAsync(string.Format(GetProductByIdUrl, ProductId)); 
        if (httpResponseMessage != null && 
          httpResponseMessage.IsSuccessStatusCode) 
        { 
          var payload = await httpResponseMessage.Content.ReadAsStringAsync(); 
          var product = JsonConvert.DeserializeObject<Product>(payload); 
          Products = new ObservableCollection<Product> {product}; 
        } 
        Message.Text = Products == null || Products.Count == 0 ? 
                 "No product retrieved" : 
                 string.Format("{0} product(s) successfully retrieved.", 
                 Products.Count); 
      } 
      catch (Exception ex) 
      { 
        Message.Text = ex.Message; 
      } 
      ProductList.ItemsSource = Products; 
    } 
 
    private async void GetProductsByCategory() 
    { 
      try 
      { 
        Message.Text = "Retrieving Products..."; 
        var httpResponseMessage = await App.HttpClient.GetAsync(string.Format(GetProductsByCategoryUrl, Category)); 
        if (httpResponseMessage != null && 
          httpResponseMessage.IsSuccessStatusCode) 
        { 
          var payload = await httpResponseMessage.Content.ReadAsStringAsync(); 
          Products = JsonConvert.DeserializeObject<ObservableCollection<Product>>(payload); 
        } 
        Message.Text = Products == null || Products.Count == 0 ? 
                 "No product retrieved" : 
                 string.Format("{0} product(s) successfully retrieved.", 
                 Products.Count); 
      } 
      catch (Exception ex) 
      { 
        Message.Text = ex.Message; 
      } 
      ProductList.ItemsSource = Products; 
    } 
 
    void MainPage_Loaded(object sender, RoutedEventArgs e) 
    { 
      if (!registered) 
      { 
        registered = true; 
        RegisterChannel(); 
      } 
      if (!string.IsNullOrWhiteSpace(ProductId)) 
      { 
        GetProductById(); 
        ProductId = null; 
        return; 
      } 
      if (!string.IsNullOrWhiteSpace(Category)) 
      { 
        GetProductsByCategory(); 
        Category = null; 
        return; 
      } 
      if (Products == null) 
      { 
        GetAllProducts(); 
      } 
    } 
 
    private void New_Click(object sender, EventArgs e) 
    { 
      NavigationService.Navigate(new Uri("/NewProductPage.xaml", UriKind.Relative)); 
    } 
 
    private void Refresh_Click(object sender, EventArgs e) 
    { 
      GetAllProducts(); 
    } 
 
    private void ProductList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) 
    { 
      if (ProductList.SelectedItem == null || !(ProductList.SelectedItem is Product)) 
      { 
        return; 
      } 
      var product = ProductList.SelectedItem as Product; 
      var index = Products.IndexOf(product); 
      NavigationService.Navigate(new Uri(string.Format("/ProductPage.xaml?selectedProduct={0}", index),  
                     UriKind.Relative)); 
      ProductList.SelectedItem = null; 
    } 
 
    private void Search_Click(object sender, EventArgs e) 
    { 
      NavigationService.Navigate(new Uri("/SearchPage.xaml", UriKind.Relative)); 
    } 
 
    private void Settings_Click(object sender, EventArgs e) 
    { 
      NavigationService.Navigate(new Uri("/SettingsPage.xaml", UriKind.Relative)); 
    } 
 
    private void Info_Click(object sender, EventArgs e) 
    { 
      NavigationService.Navigate(new Uri("/InfoPage.xaml", UriKind.Relative)); 
    } 
 
    private void RegisterChannelWithNotificationHub(string channelUri) 
    { 
      Dispatcher.BeginInvoke(async () => 
      { 
        try 
        { 
          Message.Text = "Registering with notification hub..."; 
          var hub = new NotificationHub(NotificationHubName, ConnectionString); 
          await hub.RegisterTemplateAsync(channelUri,  
                          ToastNotificationBodyTemplate, 
                          ToastNotificationTemplateName, 
                          new []{"productservice"}); 
          await hub.RegisterTemplateAsync(channelUri, 
                          LiveTileBodyTemplate, 
                          LiveTileTemplateName, 
                          new[] { "productservice" }); 
        } 
        catch (Exception) 
        { 
          Message.Text = "Unable to receive push notifications"; 
        } 
      }); 
    } 
 
    private void PushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e) 
    { 
      var channelUri = (HttpNotificationChannel)sender; 
      channelUri.BindToShellToast(); 
      channelUri.BindToShellTile(); 
      RegisterChannelWithNotificationHub(channelUri.ChannelUri.ToString()); 
    } 
 
    private void RegisterChannel() 
    { 
      Message.Text = "Opening notification channel..."; 
      var channel = HttpNotificationChannel.Find("MyPushChannel"); 
      if (channel == null) 
      { 
        channel = new HttpNotificationChannel("MyPushChannel"); 
        channel.ChannelUriUpdated += PushChannel_ChannelUriUpdated; 
        channel.ErrorOccurred += (o, args) => Dispatcher.BeginInvoke(() => 
        { 
          Message.Text = args.Message; 
        }); 
 
        // Register for this notification only if you need to receive 
        // the notifications while your application is running. 
        channel.ShellToastNotificationReceived += (o, args) => Dispatcher.BeginInvoke(() => 
        { 
          if (!args.Collection.Any()) 
          { 
            return; 
          } 
          var builder = new StringBuilder(); 
          foreach (var item in args.Collection) 
          { 
            builder.AppendLine(item.Value); 
          } 
          Message.Text = builder.ToString(); 
        }); 
        channel.Open(); 
      } 
      else 
      { 
        RegisterChannelWithNotificationHub(channel.ChannelUri.ToString()); 
      } 
      Message.Text = "Notification channel opened"; 
    } 
    #endregion 
  } 
}
NOTE: make sure to replace the following placeholders in the MainPage.xaml.cs file:
  • [YOUR-NOTIFICATION-HUB-NAME]: specify the name of the notification hub used to send push notifications.
  • [YOUR-NOTIFICATION-HUB-CONNECTION-STRING]: specify the DefaultFullSharedAccessSignature connection string of the notification hub used to send push notifications. 

The following table contains the code of the ProductPage.xaml.cs file. This page can be used to read, delete, update, navigate data of products. For brevity, I omit the code of the other pages of the application.

#region Using Directives 
using System; 
using System.Net.Http; 
using System.Text; 
using System.Windows.Controls; 
using System.Windows.Input; 
using System.Windows.Navigation; 
using Newtonsoft.Json; 
#endregion 
 
namespace ProductWindowsPhoneApp 
{ 
  public partial class ProductPage 
  { 
    #region Private Constants 
    private const string ProductUrl = "api/products/{0}"; 
    #endregion 
 
    #region Public Constructor 
    private int index; 
    #endregion 
 
    #region Public Constructor 
    public ProductPage() 
    { 
      InitializeComponent(); 
    }  
    #endregion 
 
    #region Protected Methods 
    protected override void OnNavigatedTo(NavigationEventArgs e) 
    { 
      string selectedProduct; 
      if (!NavigationContext.QueryString.TryGetValue("selectedProduct", out selectedProduct)) 
      { 
        return; 
      } 
      if (!int.TryParse(selectedProduct, out index)) 
      { 
        return; 
      } 
      GetProduct(); 
    }  
    #endregion 
 
    #region Private Methods 
    private async void Save_Click(object sender, EventArgs e) 
    { 
      var focusObj = FocusManager.GetFocusedElement(); 
      if (focusObj is TextBox) 
      { 
        var binding = (focusObj as TextBox).GetBindingExpression(TextBox.TextProperty); 
        binding.UpdateSource(); 
      } 
      var product = MainPage.Products[index]; 
      if (product == null) 
      { 
        return; 
      } 
      if (string.IsNullOrWhiteSpace(product.Name)) 
      { 
        Message.Text = "Name cannot be null"; 
        return; 
      } 
      if (string.IsNullOrWhiteSpace(product.Category)) 
      { 
        Message.Text = "Category cannot be null"; 
        return; 
      } 
      try 
      { 
        Message.Text = string.Format("Updating {0}...", product.Name); 
        var json = JsonConvert.SerializeObject(product); 
        var httpResponseMessage = await App.HttpClient.PutAsync(string.Format(ProductUrl, product.ProductId),  
                                    new StringContent(json,  
                                              Encoding.UTF8,  
                                              "application/json")); 
        if (httpResponseMessage != null && 
          httpResponseMessage.IsSuccessStatusCode) 
        { 
          Message.Text = string.Format("{0} successfully updated.", product.Name); 
        } 
      } 
      catch (Exception ex) 
      { 
        Message.Text = ex.Message; 
      } 
    } 
 
    private async void Delete_Click(object sender, EventArgs e) 
    { 
      var product = MainPage.Products[index]; 
      if (product == null) 
      { 
        return; 
      } 
      try 
      { 
        Message.Text = string.Format("Deleting {0}...", product.Name); 
        var httpResponseMessage =  
                await App.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Delete,  
                                                                      string.Format(ProductUrl,  
                                                                                    product.ProductId))); 
        if (httpResponseMessage == null ||  
          !httpResponseMessage.IsSuccessStatusCode) 
        { 
          return; 
        } 
        Message.Text = string.Format("{0} successfully deleted.", product.Name); 
        MainPage.Products.Remove(product); 
        if (MainPage.Products.Count == 0) 
        { 
          NavigationService.GoBack(); 
          return; 
        } 
        if (index >= MainPage.Products.Count) 
        { 
          index = MainPage.Products.Count - 1; 
        } 
        GetProduct(); 
      } 
      catch (Exception ex) 
      { 
        Message.Text = ex.Message; 
      } 
    } 
 
    private void Back_Click(object sender, EventArgs e) 
    { 
      index = index == 0 ? MainPage.Products.Count - 1 : index - 1; 
      GetProduct(); 
    } 
 
    private void Next_Click(object sender, EventArgs e) 
    { 
      index = index == MainPage.Products.Count - 1 ? 0 : index + 1; 
      GetProduct(); 
    }  
 
    private void GetProduct() 
    { 
      var product = MainPage.Products[index]; 
      ProductId.DataContext = product; 
      ProductName.DataContext = product; 
      Category.DataContext = product; 
      Price.DataContext = product; 
    } 
    #endregion 
  } 
}
NOTE: this Windows Phone 8 app can be used also with the following three mobile services as they share the same interface of the ASP.NET Web API RESTful service used in this sample. It's sufficient to change the Custom API to the Settings page.

When a new item is added to the ProductDb, the ASP.NET Web API RESTful service sends a notification. The Windows Phone 8 app registers two templates with the notification hub, one to receive toast notification and one to receive live tiles, as shown in the picture below.

Conclusions

This article shows how ASP.NET Web API RESTful service and Windows Azure Web Sites can be used to create mobile applications for the enterprise. You can download the code from MSDN Code Gallery. As an alternative, you can use a Windows Azure Mobile Service to create mobile applications for the enterprise. For more information, see the following articles:

 

  • Fantastic end-to-end overview and detailed review. Kudos Paolo!!

  • Thanks Erl ;)

  • How can I initiate the notification on the server side? For example if there is a new product, how can I push the message to a javascript client?  (which is I suppose can be connected to an ASP.NET MVC app via SignalR)

  • Hi Alex

    in order to send notifications to a HTML5/JS you can 't use Notification Hubs as they intended to be used only with mobile devices. Hence, as you said, I believe you should use a mechanism that allows the site, in this case the ASP.NET MVC app, to send a message to the JS client. SignalR and WebSockets can be used to this purpose. I didn't implement this pattern in my sample.

    Ciao

    Paolo

Page 1 of 1 (4 items)
Leave a Comment
  • Please add 7 and 3 and type the answer here:
  • Post
Search Blogs