It has been fun discovering Service Bus Notifications. I have received some good feedback over Twitter recently and have also shown my demo to some colleagues over the past week with some great responses.
In my last post we discussed the solution at a high level. Within this post we are going focus on the actual implementation. I will warn you in advance that this post will be fairly lengthy so buckle up. If you find the walkthrough too long, be sure to check the video at the end of the post that will show this solution live in action.
I am going to break this post up into 5 Sections:
Section 1: Creating Service Bus Queues
The area that we are going to focus on in this section is highlighted below in green. In total we are going to create 3 Queues.
The 3 Queues are called:
The Queues were created from the WindowsAzure.com portal using the default settings.
Section 2: Creating Customer Application
Moving on, we are going to focus on the Customer facing application. In this case I am running the application on the SurfaceRT but you certainly do not require a SurfaceRT to run it. In my case I just needed to ensure that my app is being compiled for ARM as opposed to x64.
I will also caution that I am by no means a Windows 8 store app expert. So if you are looking for a fancy UI or UX you aren’t going to find it here. I approaching this blog post from an integration perspective.
In order to build this sample you will need a Windows Store development account. If you have an MSDN account you already have some entitlements so it won’t cost you extra. Otherwise you can expect to pay some money(it may vary by country so I will just leave it there).
Let’s get started:
private void btsSubmitOutage_Click(object sender, RoutedEventArgs e){ txtStatus.Text = ""; CustomerPowerOutage cust = new CustomerPowerOutage(); cust.SiteID = txtSiteId.Text; cust.CustomerName = txtName.Text; cust.Address = txtAddress.Text; ListBoxItem selected =(ListBoxItem) listCity.SelectedItem; cust.City = selected.Content.ToString();
bool requestETR = (bool) chkETR.IsChecked; bool requestPowerOnConfirmation = (bool) chkConfirmPowerOn.IsChecked;
SendMessageToQueue(cust,requestETR,requestPowerOnConfirmation); }
namespace CustomerPowerOutageApp{ public class CustomerPowerOutage { public string SiteID { get; set; } public string CustomerName { get; set; } public string Address {get;set;} public string City { get; set; } }}
private async void SendMessageToQueue(CustomerPowerOutage cust,bool requestETR, bool requestPowerOnConfirmation) { Message m = new Message(cust, new DataContractSerializer(typeof(CustomerPowerOutage))); m.Properties.Add("RequestETR", requestETR); m.Properties.Add("RequestPowerOnConfirmation", requestPowerOnConfirmation); await CustomerQueue.SendAsync(m); txtStatus.Text = "Power Outage Ticket has been successfully received."; }
private Queue CustomerQueue = null; public GroupedItemsPage() { this.InitializeComponent(); CustomerQueue = new Queue("CustomerQueue", "Endpoint=sb://<your_namespace>.servicebus.windows.net/;SharedSecretIssuer=owner;SharedSecretValue=<your_key>"); }
At this point we have built the GUI, created a contract that we can use to exchange data with BizTalk, and sent a message to Service Bus Queue. What is missing though is that our Customer Power Outage Application will receive Service Bus Notifications (Toast Notifications). This is represented as step 11 in the Solution Overview images. Since we are in this application, lets do this now.
At this point we have finished our configuration for Service Bus Notification Hubs. We now need to wire up some code within our application in order to take advantage of this configuration.
using Microsoft.WindowsAzure.Messaging;using System.Threading.Tasks;using Windows.Data.Xml.Dom;using Windows.UI.Notifications;
NotificationHub notificationHub;
/// <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() { var cn = ConnectionString.CreateUsingSharedAccessSecretWithListenAccess( "sb://<your_namespace>.servicebus.windows.net/ ", "<your_NotificationHubCredentials>); notificationHub = new NotificationHub("PowerOutageNotificationhub", cn);
this.InitializeComponent(); this.Suspending += OnSuspending; }
protected override async void OnLaunched(LaunchActivatedEventArgs args) {
await InitializeNotificationsAsync();
……
protected async override void OnActivated(IActivatedEventArgs args) { base.OnActivated(args); await InitializeNotificationsAsync(); }
async Task InitializeNotificationsAsync() { await notificationHub.RefreshRegistrationsAsync();
if (!await notificationHub.RegistrationExistsForApplicationAsync( "PowerOutageAppToastReg")) {
await notificationHub.CreateTemplateRegistrationForApplicationAsync( BuildTextToastTemplate(), "PowerOutageAppToastReg", new string[] {"0090123456789"}); //Our Customer Site ID } }
XmlDocument BuildTextToastTemplate(){ var template = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02); var textNode = template.SelectSingleNode("//text[@id='1']") as XmlElement; if (textNode != null) { textNode.InnerText = "$(msg)"; }
var textNode2 = template.SelectSingleNode("//text[@id='2']") as XmlElement; if (textNode2 != null) { textNode2.InnerText = "$(msg2)"; }
return template;}
Section 3: Creating PLT Application
Note: There are aspects of this application that are very similar to the Customer Power Outage scenario so those parts will not be duplicated. I will try to do a good job of communicating when this happens.
The purpose of this application is that we will have a Power Line Technician (PLT) that will receive a notification indicating that he/she has work to do. When the PLT is in their application, they will be able to retrieve their order from the Service Bus queue. As they update the order or close it the information will be sent back to the Service Bus and subsequent Notifications will be sent to the Customer who logged the Power Outage.
private void btnRetrieveNextOrder_Click(object sender, RoutedEventArgs e) {
ReceiveMessageFromQueue(); }
private async void ReceiveMessageFromQueue(){
var message = await cwo.ReceiveAsync<Message>(new System.TimeSpan(0,0,5));
var data = message.GetBody<UpdateOrder>(new DataContractSerializer(typeof(UpdateOrder)));
txtAddress.Text = data.Address; txtSiteID.Text = data.SiteID; txtName.Text = data.CustomerName; txtCity.Text = data.City;
//Retrieve the BrokeredMessaging Properties so that we can later be used by BizTalk to determine whether or not Notifications should be send
isETRRequired =(bool) message.Properties["RequestETR"]; isPowerRestoreRequired = (bool) message.Properties["RequestPowerOnConfirmation"];
//display controls lblETR.Visibility = Windows.UI.Xaml.Visibility.Visible; lblETR2.Visibility = Windows.UI.Xaml.Visibility.Visible; txtETR.Visibility = Windows.UI.Xaml.Visibility.Visible; btnUpdateETR.Visibility = Windows.UI.Xaml.Visibility.Visible;
lblPowerRestored.Visibility = Windows.UI.Xaml.Visibility.Visible; txtPowerRestored.Visibility = Windows.UI.Xaml.Visibility.Visible; lblETR2_Copy.Visibility = Windows.UI.Xaml.Visibility.Visible; btnCloseOrder.Visibility = Windows.UI.Xaml.Visibility.Visible; imgMap.Visibility = Windows.UI.Xaml.Visibility.Visible; txtStatus.Visibility = Windows.UI.Xaml.Visibility.Collapsed; }
namespace PowerLineTechnicianApp{ public class UpdateOrder { public string SiteID { get; set; } public string CustomerName { get; set; } public string Address { get; set; } public string City { get; set; } public double ETR { get; set; } public double PowerOutDuration { get; set; } public string OrderAction { get; set; } public bool NotifyETR { get; set; } public bool NotifyPowerRestored { get; set; } }}
private void btnUpdateETR_Click(object sender, RoutedEventArgs e) {
UpdateOrder uo = new UpdateOrder();
uo.Address = txtAddress.Text; uo.City= txtCity.Text; uo.CustomerName = txtName.Text; uo.SiteID = txtSiteID.Text; uo.OrderAction = "UPDATE"; uo.ETR = System.Convert.ToDouble(txtETR.Text); uo.NotifyETR = isETRRequired; uo.NotifyPowerRestored = isPowerRestoreRequired;
SendMessageToQueue(uo);
}
private async void SendMessageToQueue(UpdateOrder uo ){
Message m = new Message(uo, new DataContractSerializer(typeof(UpdateOrder)));
//Send message Asynchronously await uwo.SendAsync(m); txtStatus.Visibility = Windows.UI.Xaml.Visibility.Visible; txtStatus.Text = "Update Successfully Sent to Outage Management System";
txtETR.Text = ""; txtPowerRestored.Text = "";}
private void btnCloseOrder_Click(object sender, RoutedEventArgs e){
txtETR.Text = ""; txtStatus.Text = "";
uo.Address = txtAddress.Text; uo.City = txtCity.Text; uo.CustomerName = txtName.Text; uo.SiteID = txtSiteID.Text; uo.OrderAction = "CLOSED"; uo.PowerOutDuration = System.Convert.ToDouble(txtPowerRestored.Text); uo.NotifyETR = isETRRequired; uo.NotifyPowerRestored = isPowerRestoreRequired;
Queue cwo; //Create Work Order QueueQueue uwo; //Update Work Order Queuebool isETRRequired = false; //Used to store the value coming from the Brokered Message Propertybool isPowerRestoreRequired = false; //Used to store the value coming from the Brokered Message Property
//Constructorpublic GroupedItemsPage(){ cwo = new Queue("CreateWorkOrder", "Endpoint=sb://<your_namespace>.servicebus.windows.net/;SharedSecretIssuer=owner;SharedSecretValue=<your_key>");
uwo = new Queue("UpdateWorkOrder", "Endpoint=sb://<your_namespace>.servicebus.windows.net/;SharedSecretIssuer=owner;SharedSecretValue=<your_key>");
this.InitializeComponent();}
using Microsoft.WindowsAzure.Messaging;using Windows.UI.Notifications;using Windows.Data.Xml.Dom;using System.Threading.Tasks;
sealed partial class App : Application{
/// <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() { var cn =ConnectionString.CreateUsingSharedAccessSecretWithListenAccess("sb://<your_namespace>.servicebus.windows.net/ ","<your_listenkey>"); notificationHub = new NotificationHub("workorderhub", cn);
protected override async void OnLaunched(LaunchActivatedEventArgs args) { await InitializeNotificationsAsync();
……..
if (!await notificationHub.RegistrationExistsForApplicationAsync( "WorkOrderAppToastReg")) {
await notificationHub.CreateTemplateRegistrationForApplicationAsync( BuildTextToastTemplate(), "WorkOrderAppToastReg", new string[] { "Airdrie" });
} }
Section 4: Creating BizTalk Application
BizTalk is the ‘glue’ that is used to tie this all together. BizTalk will perform the following functions:
using Microsoft.ServiceBus;using Microsoft.ServiceBus.Notifications;
public static void SendWorkOrderNotifications(string City, string Address){
var cn = ServiceBusConnectionStringBuilder.CreateUsingSharedAccessSecretWithFullAccess("<your_namespace>", "<Full_AccessKey”>); var hubClient = NotificationHubClient.CreateClientFromConnectionString(cn, "workorderhub"); hubClient.SendTemplateNotification(new Dictionary<string, string> { {"msg", "New Power Outage has been reported"}, {"msg2","Location of outage is: " + Address + ", " + City} }, "Estimated Time Of Restore", City);
public static void SendEstimatedTimeOfRestore(string SiteID,DateTime etr){
var cn = ServiceBusConnectionStringBuilder.CreateUsingSharedAccessSecretWithFullAccess("<your_namespace>", "<Full_AccessKey"); var hubClient = NotificationHubClient.CreateClientFromConnectionString(cn, "PowerOutageNotificationhub"); hubClient.SendTemplateNotification(new Dictionary<string, string> { {"msg", "Power Outage Estimated Time of Restore"}, {"msg2","Your estimated time of restore is: " + etr.ToString()} }, "Estimated Time Of Restore", SiteID);
public static void SendPowerOutageComplete(string SiteID, double duration){
var cn = ServiceBusConnectionStringBuilder.CreateUsingSharedAccessSecretWithFullAccess("<your_namespace>", "<Full_AccessKey"); var hubClient = NotificationHubClient.CreateClientFromConnectionString(cn, "PowerOutageNotificationhub"); hubClient.SendTemplateNotification(new Dictionary<string, string> { {"msg", "Power Outage has been resolved"}, {"msg2","Your total outage time was: " + duration +" hours"} }, "Estimated Time Of Restore", SiteID);
Testing: Since there are a lot of moving parts I am going to try something new and record the interactions between these systems. You can view the video below:
Conclusion
Overall I am very happy with the way that this solution works. The funny thing is that about 1.5 years ago at my previous organization we were thinking about doing this exact scenario and supporting mobile devices. Unfortunately that project never came to fruition but now the Service Bus Notification Hubs are out it makes it a lot easier.
Another important takeaway is that there can be some very important information that is moving through BizTalk. I hope I have demonstrated that it isn’t to hard to use this information to generate Toast Notifications. This concept isn’t all that new when you thing of BAM Alerting. The difference is that we are now longer bound to just emails and can tailor an even better user experience through Toast Notifications. Another opportunity that was not explored in this post(but maybe a future post) is that we can also update Live Tiles through this same mechanism which will only enhance this user experience.
I hope you have enjoyed this post as much as I have enjoyed putting it together.