Freigeben über


Phone + Cloud Series: Bringing It All Together With Windows Phone 7

Looking Back

Over the course of this series, we’ve built a SQL Azure database, a Windows Azure worker role, and a WCF web role hosted in Azure. We are fully cloud-enabled at this point! Although, you’ll likely be running this all in the development fabric rather than real Azure until you have a need for enterprise-grade scalability.

Here’s a rough look at what we are working with right now, in a conceptual sense. Explained below the break:

arch

Here’s the diagram explained.

  • The SQL Azure data store holds information about our users: their username, password, stocks they are watching and their push notification preferences.
  • The Azure Worker Role is responsible for monitoring stocks for each user in the database. To get stock prices, it reads from the publicly available Yahoo! stock quote API.
  • When the Azure Worker Role finds a user to send a push notification to, it sends an HTTP POST to the URI associated with the user’s push notification URI.
  • The Microsoft Push Notification Service then sends the push notification to the device (or emulator) based on whatever voodoo magic is used to make that happen.

Of course, our device will never receive push notifications if the Azure service doesn’t know about our device, or that we want push notifications. We have to have fields in the database available for the Azure service to, in turn, invoke the Microsoft push notification service. So…

  • When the user launches the Windows Phone 7 application, the user will have an opportunity to enter their information. When the user presses Register, the data will be sent to the WCF Service registration endpoint where the user will be registered (entered into the SQL Azure database). Now, when the Azure worker role goes on its next pass of checking for stock updates, our user will be included.

Creating The Windows Phone 7 Application

imageWe’ll add one more project to our solution: a Windows Phone 7 Silverlight application. It can be a pivot or a panorama, or a blank app; really it doesn’t matter because we are only going to be working on one screen through the end of this application. I chose to use a Pivot so that you can see on another tab where you might implement the rest of the app (i.e. seeing your stock quotes in one place, for example). Aside from subscribing to push notifications, our app won’t do much, this exercise is just to illustrate what you need to do to subscribe to push notifications.

I marked up MainPage.xaml in Expression Blend to look like the image to the left here.

It’s quite simple – when the user fills in information and clicks Register, their password is hashed and the UserService is called. If the account exists and the password is correct, the data is updated. If the account doesn’t exist, it is created.

The Monitor tab on the right just has a cheeky message like “This is where you’d implement useful stuff that makes the app worth having.” But you might have a nice visual list of stock prices that can be refreshed, or have trends, etc. Those are things you probably know how to do, and is ancillary to the point of this article.

Now that you see how the screen looks, let’s look at how to go about interfacing with these services.

Understanding The App’s External Connection Points

Thankfully, this application only has two external connection points of interest. The first is with the Microsoft push notification service. While we do need to open / find a notification channel to use, we don’t really have to do anything else – no adding of service references or anything like that. It just pretty much works. The other connection point is to the UserService WCF web role we hosted in Azure. We do need to add a service reference here, and we’ll call it asynchronously to register the user’s preferences.

Adding A Service Reference

Your main purpose of working with the WCF web role is to register the user and application with the SQL Azure database so that the Microsoft Push Notification Service knows how to get back to the phone to send notifications. We have to add a service reference, which will generate a proxy class which we’ll use to call the RegisterPushReg service operation.

If you right click on the WP7 project’s references and choose Add Service Reference, you’ll get a dialog like the one below. Click the little Discover button to search your solution for services, and choose UserService.

image

After clicking OK, you’ll be able to use this service. We’ll do that in just a little bit. First, we have to discuss how we are going to tackle the main problem that faces us: how do we get the push notification URI for this device and application?

Obtaining the Push Notification URI

I did this the easy way. In the PhoneApplicationPage_Loaded event (which I generated in Blend), I make a call to new method called GetPushNotificationURI. I do this asynchronously because I did something I shouldn’t have: I assume that I will get the URI back from the service before the user clicks Register. You know better than that, and will find another way of doing this, or will check for null when you need to use the member pushNotificationUri variable.

 private void PhoneApplicationPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
    GetPushNotificationURI();
}

 

The below code for GetPushNotificationURI is quite simple. Make sure you are using Microsoft.Phone.Notification; at the top.

 

  • Line 3 defines a channel name, which can be whatever you like. You want it to be as unique as possible because the push notification service will send a notification to a “channel” on your phone which will map to an application.
  • On line 6, we attempt to find an existing channel. If we don’t do this check, we can get errors. The channels do not stay around forever, though, so you need to do this anyway. If the channel changes, you should consider updating the URI in your database.
  • On line 10, if no channel exists, we create a new channel.
  • On line 11, we subscribe to the ChannelUriUpdated event of the channel which will finally give us access to the URI itself. Note that we can’t get that from within this method so we can’t use it as a return value, which would make sense.
  • On line 13 we bind the channel to toast notifications, so this channel will be used for toast notifications for our app.
  • On line 19, we set the push notification URI (a private class variable) to the URI that the service gives us.
    1: private void GetPushNotificationURI()
    2: {
    3:     string channelName = "AzureStockTestChannel";
    4:     HttpNotificationChannel channel; 
    5:     channel = HttpNotificationChannel.Find(channelName);
    6:     if (channel != null)
    7:         pushNotificationUri = channel.ChannelUri.ToString();
    8:     else
    9:     {
   10:         channel = new HttpNotificationChannel(channelName);
   11:         channel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(channel_ChannelUriUpdated);
   12:         channel.Open();
   13:         channel.BindToShellToast();
   14:     }
   15: }
   16:  
   17: void channel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
   18: {
   19:     pushNotificationUri = e.ChannelUri.ToString();
   20: }

 

Registering The Device With Our WCF Service

Now that we have the push notification URI, we can send it to the WCF service which will enter it into our SQL Azure database for use by our Azure worker role (phew!)

This is actually the really easy part.

Remember, we created a service reference, so we have this lovely proxy class to work with. (When you finally move this to Azure you will need to configure your service reference with the updated URL).

All of our service methods are called in an asynchronous manner, and they totally should be on a phone – think about the implications of having a blocking service call with no connectivity… yuck! Because of this, we have to subscribe to the _Completed events that are generated in our proxy class.

Here’s the code for the btnRegister_Click event handler:

  • On line 3, we instantiate our client proxy class UserServiceClient.
  • On lines 3 and 4, we subscribe to Completed events for the two service operations we are using: RegisterForPush and UnregisterForPush.
  • On line 7, we check to see if the user wants push notifications. Their choice impacts which method we call.
    1: private void btnRegister_Click(object sender, System.Windows.RoutedEventArgs e)
    2: {
    3:     UserService.UserServiceClient client = new UserService.UserServiceClient();
    4:     client.RegisterForPushCompleted += new EventHandler<UserService.RegisterForPushCompletedEventArgs>(client_RegisterForPushCompleted);
    5:     client.UnregisterForPushCompleted += new EventHandler<UserService.UnregisterForPushCompletedEventArgs>(client_UnregisterForPushCompleted);
    6:  
    7:     if (cbUsePushNotifications.IsChecked.Value == true)
    8:     {
    9:         client.RegisterForPushAsync(tbUsername.Text, tbStocks.Text, PasswordHash(tbPassword.Text), pushNotificationUri);
   10:     }
   11:     else
   12:     {
   13:         client.UnregisterForPushAsync(tbUsername.Text, PasswordHash(tbPassword.Text));
   14:     }
   15: }
   16:  

Here are the event handlers I implemented for the Completed events (they just show a MessageBox with the results)

    1: void client_RegisterForPushCompleted(object sender, UserService.RegisterForPushCompletedEventArgs e)
    2: {
    3:     if (e.Result == true)
    4:         MessageBox.Show("Registered for push notifications");
    5:     else
    6:         MessageBox.Show("An error occurred: " + e.Error.ToString());
    7: }
    8:  
    9: void client_UnregisterForPushCompleted(object sender, UserService.UnregisterForPushCompletedEventArgs e)
   10: {
   11:     if (e.Result == true)
   12:         MessageBox.Show("Unregistered for push notifications");
   13:     else
   14:         MessageBox.Show("An error occurred: " + e.Error.ToString());
   15: }

The rest of the code here is rather uninteresting. The only other code that does anything is PasswordHash which generates a SHA1 hash of the user’s password so we are not sending unencrypted passwords over the air:

    1: //...
    2: using System.Security.Cryptography;
    3: //...
    4:  
    5: private string PasswordHash(string input)
    6: {
    7:     HashAlgorithm algorithm = new SHA1Managed();
    8:     string result = "";
    9:     using (algorithm)
   10:     {
   11:         byte[] bytes = Encoding.UTF8.GetBytes(input);
   12:         result = BitConverter.ToString(algorithm.ComputeHash(bytes));
   13:     }
   14:     return result;
   15: }

And We’re Done!

We are DONE! In the next post, you’ll see a video recap with a walkthrough of the code and what it looks like when it is all running together.