Using Windows Azure Service Bus Topics in distributed systems

Service Bus topics allow to perform one way communication using publish/subscribe model. In that model the Service Bus topic can be treated as a intermediary queue that multiple users/components can subscribe to.

When publishing a message we can choose to route it to all subscribers or to apply filters for each subscription resulting in each subscriber receiving messages that are addressed to him. With Service Bus topics we can easily scale distributed applications communicating with each other within or across multiple networks.

In this article I will show you how to build and test Service Bus topic on your local computer. In our example we will simulate sending messages from the web, mobile and service application to the Service Bus Topic.

These messages will be then routed to relevant subscriptions based on defined filters we assigned for each of them. Subscription for messages from the web application will be using multiple auto scalable worker roles to process the business logic. Same will apply for service messages. If we don’t expect a lot of traffic coming from mobile application, we can then use single worker role (with failover settings).

Autoscaling worker roles can be performed using Enterprise Library 5.0 – Autoscaling Application Block (aka WASABi). This will ensure that appropriate number of worker roles will be automatically started when traffic increases and stopped if the traffic will ease.

See high level architecture diagram below:

ServiceBusTopic

 

In order to start, we need to first install “Service Bus 1.0 for Windows Server” (runs on Win7 as well). After installation please go to start > Service Bus 1.0 > Service Bus Configuration. Then use the wizard to set up the web farm first and after that, join your computer to that farm – this will essentially create namespace within the namespace on your local machine.

ServiceBus-configuration

After you configure the Service Bus installation you will get endpoint address that your local application will use to connect to the Service Bus. You may notice that after installation there are 2 databases created on you local MSSQL server, see below image:

ServiceBus-tables

In order to connect to our Service Bus we will use the function below. This will create connection string the pass in to the NamespaceManager class.

  //use this setting when deploying to Windows Azure
  <add key="Microsoft.ServiceBus.ConnectionString" value="Endpoint=sb://[your namespace].servicebus.windows.net;SharedSecretIssuer=owner;SharedSecretValue=[your secret]" />

 public static string getLocalServiceBusConnectionString()
 {
    var ServerFQDN = System.Net.Dns.GetHostEntry(string.Empty).HostName;
    var ServiceNamespace = "ServiceBusDefaultNamespace";
    var HttpPort = 9355;
    var TcpPort = 9354;

    var connBuilder = new ServiceBusConnectionStringBuilder();
    connBuilder.ManagementPort = HttpPort;
    connBuilder.RuntimePort = TcpPort;
    connBuilder.Endpoints.Add(new UriBuilder() { Scheme = "sb", Host = ServerFQDN, Path = ServiceNamespace }.Uri);
    connBuilder.StsEndpoints.Add(new UriBuilder() { Scheme = "https", Host = ServerFQDN, Port = HttpPort, Path = ServiceNamespace }.Uri);

    return connBuilder.ToString();
 }

Within the worker role we will use NamespaceManager to create Service Bus Topic (if does not exist). We will also create subscriptions and associated filters.
Please notice that subscription will filter messages using MessageOrigin property. This property will be assigned to the message in the message send method for each application separately (web, mobile, service).

 public void CreateServiceBusTopicAndSubscriptions(NamespaceManager namespaceManager)
 {
    #region Configure and create Service Bus Topic
    var serviceBusTestTopic = new TopicDescription(TopicName);
    serviceBusTestTopic.MaxSizeInMegabytes = 5120;
    serviceBusTestTopic.DefaultMessageTimeToLive = new TimeSpan(0, 1, 0);

    if (!namespaceManager.TopicExists(TopicName))
    {
        namespaceManager.CreateTopic(serviceBusTestTopic);
    }
    #endregion

    #region Create filters and subsctiptions
    //create filters
    var messagesFilter_Web = new SqlFilter("MessageOrigin = 'Web'");
    var messagesFilter_Mobile = new SqlFilter("MessageOrigin = 'Mobile'");
    var messagesFilter_Service = new SqlFilter("MessageOrigin = 'Service'");

    if (!namespaceManager.SubscriptionExists(TopicName, "WebMessages"))
    {
        namespaceManager.CreateSubscription(TopicName, "WebMessages", messagesFilter_Web);
    }

    if (!namespaceManager.SubscriptionExists(TopicName, "MobileMessages"))
    {
        namespaceManager.CreateSubscription(TopicName, "MobileMessages", messagesFilter_Mobile);
    }

    if (!namespaceManager.SubscriptionExists(TopicName, "WCfServiceMessages"))
    {
        namespaceManager.CreateSubscription(TopicName, "WCfServiceMessages", messagesFilter_Service);
    }
    #endregion
}

We also need to create subscription clients in “OnStart” method giving each subscriber a unique name.

 public override bool OnStart()
 {
    // Set the maximum number of concurrent connections 
    ServicePointManager.DefaultConnectionLimit = 12;

    // Create the queue if it does not exist already
    //string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
    var connectionString = getLocalServiceBusConnectionString();
    var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);

    //create topic and subscriptions
    CreateServiceBusTopicAndSubscriptions(namespaceManager);

    // Initialize subscription for web, mobile and service
    SubscriptionClients.Add(SubscriptionClient.CreateFromConnectionString(connectionString, TopicName, "WebMessages"));
    SubscriptionClients.Add(SubscriptionClient.CreateFromConnectionString(connectionString, TopicName, "MobileMessages"));
    SubscriptionClients.Add(SubscriptionClient.CreateFromConnectionString(connectionString, TopicName, "WCfServiceMessages"));

    IsStopped = false;
    return base.OnStart();
}

Inside the run method we will use Task Parallel foreach method to create separate task for each subscriber listening for incoming messages.
This is only to simulate multiple subscribers in one place. Normally each worker role will connect to the topic listening for the messages appropriate for it’s type (separate for web, mobile and service).

public override void Run()
{
 Parallel.ForEach(SubscriptionClients, currentSubscrtiption =>
 {
    while (!IsStopped)
    {
        #region Receive messages
        try
        {
            // Receive the message
            var receivedMessage = currentSubscrtiption.Receive();
         
            if (receivedMessage != null)
            {
                var messageFrom = receivedMessage.Properties["MessageOrigin"].ToString();

                switch (messageFrom)
                {
                    case "Web":
                        //send it to web processing logic

                        break;
                    case "Mobile":
                        //send it to mobile processing logic

                        break;
                    case "Service":
                        //send it to service processing logic

                        break;
                    default:
                        break;
                }

                // Process the message
                Trace.WriteLine(Environment.NewLine + "--------------------------" + Environment.NewLine);
                Trace.WriteLine(string.Format("{0} message content: {1}", messageFrom, receivedMessage.GetBody<string>()));

                receivedMessage.Complete();
            }
        }
        catch (MessagingException e)
        {
            if (!e.IsTransient)
            {
                Trace.WriteLine(e.Message);
                throw;
            }

            Thread.Sleep(10000);
        }
        catch (OperationCanceledException e)
        {
            if (!IsStopped)
            {
                Trace.WriteLine(e.Message);
                throw;
            }
        }
        #endregion
    }
});
}

Finally we can simulate sending messages from the MVC application. We will use 3 different buttons to create and send messages.

 [HttpPost]
 public ActionResult SendWebMessage()
 {
    SendMessage("Web");

    return RedirectToAction("Index", "Home");
 }

 [HttpPost]
 public ActionResult SendMobileMessage()
 {
    SendMessage("Mobile");

    return RedirectToAction("Index", "Home");
 }

 [HttpPost]
 public ActionResult SendServiceMessage()
 {
    SendMessage("Service");

    return RedirectToAction("Index", "Home");
 }

See the image below:

ServiceBusTopic-subscriptions

Please note that when sending messages we have to assign value to message.Properties[“MessageOrigin”]. This will be used by the Service Bus Topic to route messages to appropriate subscriptions.

 void SendMessage(string type)
 {
    var connectionString = getLocalServiceBusConnectionString();

    var Client = TopicClient.CreateFromConnectionString(connectionString, "ServiceBusTestTopic");

    var message = new BrokeredMessage("test message");
    message.Properties["MessageOrigin"] = type;

    Client.Send(message);
 }

As usual, I have attached working project files for your tests 🙂

AzureServiceBus

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Using Html5 Local storage on mobile devices

In many situations there is a need to store something on the mobile device that can be later retrieved and reused. Typical situation is the offline state where device suddenly loosing internet connection.

To solve this problem we can use HTML5 local storage. It works on all mobile devices with the most popular browsers. Additionally, unlike the cookies the values are not being transmitted from device to the server on each request. They are also persistent across the pages.

In our example we will assume that user sends timesheet data from his mobile device (the files are running locally on the device).
When user sends the data, we store it in local storage trying to send it. If sending is not successful, we keep it in the storage. We also check if we can send the data every time user posts the new form.

 //when DOM is ready
 $(document).ready(function () {

    //try to post saved data every time someone loads page (silent mode)
    trySaveOfflineData(true);

    //if someone saved the form
    var id = getUrlVars()["UserID"];
 
    if (id != null) {
        
        //save in local storage first
        saveData();

        //post local storage data data
        trySaveOfflineData(false);
    }
});

We also need to check if local storage is supported on the mobile device.

 function supportsLocalStorage() {
    //check if device supports local storage
    try {
        return 'localStorage' in window && window['localStorage'] !== null;
    } catch (e) {
        return false;
    }
}

When user saves the data we simply read the values and concatenate it to the single string object. We will read this values later using generic handler in our standard asp.net application located on the remote server.

 function saveData() {
    //read url params from submitted form (we can also use post method instead)
    var userID = getUrlVars()["UserID"];
    var name = getUrlVars()["Name"];
    var comments = getUrlVars()["Comments"];
    var hours = getUrlVars()["Hours"];
    var overtime = getUrlVars()["Overtime"];
    var job = getUrlVars()["Job"];

    var idKey = 'timesheet.' + userID;//set the unique key making sure it belongs to us
    var obj = userID + "|" + name + "|" + comments + "|" + hours + "|" + overtime + "|" + job;

    //save object in local storage
    localStorage.setItem(idKey, obj);

    return true;
}

Every time user loads the page we read values from the local storage and try to post it to our web application. If he gets internet connection we should be able to push our storage data to the server.

 function trySaveOfflineData(silent) {
    // check all keys in local storage
    for (var key in localStorage) {
        
        //check if it our data
        if (key.search('timesheet.') >= 0) {

            var dataObj = localStorage[key]; // get object

            //post it to the server
            postData(SERVER_URL, dataObj, silent);

            //delete item from local storage
            localStorage.removeItem(key);
        }
    }
}

When posting the data we simply use Jquery post method. Next, we read the response from the server as a string and display messages if necessary.

 function postData(url, dataObj, silent) {
    //use Jquery post and send object as a param timesheetObj
    $.post(url, { timesheetObj: dataObj },
                             //get response
                             function (data) {
                                 if (data == "OK") {
                                     if (!silent) { alert("Data has been sent!"); }
                                     return;
                                 }
                                 //if not supports local storage display warning
                                 if (!supportsLocalStorage()) {
                                     if (!silent) { alert("offline mode not supported!"); }
                                     return;
                                 }
                                 //if we got here - it has to be an error 🙂
                                 if (!silent) { alert("Error occurred: " + data); }
                             });
 }

On the server side we use generic handler to process the request. We also need to set headers to avoid Cross-site HTTP request errors.

  public void ProcessRequest(HttpContext context)
  {
        // this is to avoid following error: XMLHttpRequest cannot load https://localhost:58017/WebSite/send.ashx. 
        // Origin https://localhost:58078 is not allowed by Access-Control-Allow-Origin.
        context.Response.AppendHeader("Access-Control-Allow-Origin", "*");  
        context.Response.ContentType = "text/plain";

        try
        {
            var data = context.Request["timesheetObj"];
            var timesheet = data.Split('|');

            var userID = timesheet[0];
            var name = timesheet[1];
            var comments = timesheet[2];
            var hours = timesheet[3];
            var overtime = timesheet[4];
            var job = timesheet[5];
            
            //check and save the data here

        }
        catch (Exception ex)
        {
            context.Response.Write(ex.Message); return;
        }

        context.Response.Write("OK");
    }

Solution I described it’s just a test sample that needs to be converted using PhoneGap before going into production. PhoneGap simply wraps html elements to get desired look on the device. We would also need to apply security measures to avoid fake or duplicated requests etc.

In general I this this is good alternative to creating mobile solution that works only on one platform.

I have included working example below for you tests. Happy coding 🙂
Html5-LocalStorage

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...