In previous articles I've repeatedly hit the topic of continuous connectivity versus power management issues in Windows 8 devices. It has to be evident that it is an hard task, to manage successfully the balance between an always connected device and a long battery lifetime. It is clear that a device made with the modern constraints, needs to be almost always connected to the network to take advantage of cloud resources, given that there are really few apps that doesn't use some kind of access to these resources to empower their features. But this means that the device rapidly drain batteries also when it is not directly used by the user.
On the hard way to gain the illusion of having an always connected device, together with a light consumption of power resources, Windows 8 introduces the concept of push notifications, in a way very close to what it has been done with Windows Phone. A "push notification" architecture, moves the responibility of monitoring a resource on the cloud to a server process that is able to connect back to the client that requested this monitoring to notify that something happened. In this way, the high-consumption tasks are moved to a well-powered server-farm and the sole activity of the device is to "wait actively" for someone to call.
The architecture behind "Push Notifications"
Given that you understand the "strategy" behind the scenes of push notifications, you have to be aware that there is three actors that take part in this commedy, instead of the two you may expect.
As you may expect the main role is up to the device that is the final target of every notification. It is in charge of initiating the push notification channel and then it waits for incoming notifications and obviously to manage messages when they arrive in a way that depends on the type of notification.
Another important role is given to a "cloud service" that is in charge of the active part. Its main activity is to receive registration requests by client and to work to monitor some kind of resource and decide when a notification should occur. It is in this precise moment that the third part enter the scene.
It is called "Windows Notification Service" (WNS for friends) and it is in charge of knowing the remote endpoint assigned to each client and only its can reach the client when there is the need of sending a notification. The existence of the wns has two purposes: first of all it protect the client endpoints for unwanted accesses. There is a strong authentication process required to access the wns and to get back to clients. The other reason is to simplify the architecture required to follow back clients while they move around. The WNS is really effective in this as it gives a very straightforward interface that make the process of sending notifications a breeze.
In the figure on the left side I reported the first phase of the notification process. Its purpose is to establish a connection between parts that then are able to collaborate actively. With blue arrows, I show the part that is accomplished by the device. It authenticates itself to the WNS starting the notification channel and then it gets an unique resource identifier that represents the established connection. Then this URI is registered by the Cloud Service that shall use it every time it needs to send a notification to the client. The green arrows shows the authentication process that involves the cloud service and the WNS. Using a oauth 2.0 protocol, the cloud service is in charge to authenticate itself to the WNS, using a couple of keys provided when the application name is reserved in the marketplace. As a result it gets a token that should use every time it access the wns. After this part has been completed the notification channel is ready to be used. The client device and the WNS are connected together and the cloud service starts to monitor the resource that is the source of the notifications.
Every time it has to send a notification, the cloud service uses the URI provided during the registration and calls back the WNS, providing a message to send to the device. Important to say, it never calls directly the device but asks the WNS to make this call on behalf of it.
So, the cloud service prepares a notification. The notification can be one between "tile", "toast", "badge" and "raw". All of these types have different peculiarities that I will explain in the following paragraphs. Suffice to say that the body of the notification is a xml message for the first three types and a simple string for the "raw" notification. Then the cloud service makes a POST to the URI of every client registered, and it sends the body of the notification together with the authentication token it got by the WNS during its authentication. The WNS immediately tries the notification to the client and it answers providing a status value that informs the cloud service about the success or failure of the notification. The failure has to be managed in a number of ways. If it is a dead end, that means the client does not exists anymore, it have to remove the registration by its internal list to avoid repeated errors. At this point the client has been notified and the cloud service may returns to its activities.
After this detailed explanation it has to be clear that you're in charge of developing two of the three actors of the representation. The first that I'll cover in this article is the authentication and registration phase. In the next article I will explain the notification aspects.
Building the cloud service
Talking about cloud, I think is is really clear that the cloud service actor is something that is located somewhere, in a server-farm, but don't be fear of the "cloud" adjective because it is not required that it's hosted by a cloud provider. Also if this is probably the best chance for your service responsiveness and continuity, you are for sure able to host this actor in your own server, suffice that it is widely reachable on the Internet. Obviously you are in charge of ensuring the uptime of your service and it is the reason because I say that consider to use a cloud provider - as an example Azure, but this not really required given that the WNS is completely interoperable - is a good choice. What it is really hard to do is hosting the cloud service in a low-cost provider, not only because of the continuity requirements, but also because it has to be active in scanning a resource to decide when to raise a notification and this requires some advanced hosting features.
For the purpose of this article I will describe the use of a WCF service, exposed with a BasicHttpBinding, only for the sake of being simple and easy to understand. You have to be aware that you can write your own cloud service on almost every server platform and - given that probably is should be consumed by lot of other technologies - it should be interoperable exposing standard protocols like REST and JSON. From my example you'll can easily migrate the endpoint to a WebHttpBinding and achieve this result with a low effort. The code below shows the interface exposed by the service.
1: [ServiceContract]
2: public interface INotificationService
3: {
4: [OperationContract]
5: void Register(Uri uri);
6: [OperationContract]
7: void NotifyTile(string message);
8: [OperationContract]
9: void NotifyToast(string message);
10: [OperationContract]
11: void NotifyRaw(string message);
12: [OperationContract]
13: void NotifyBadge(string badge);
14: }
It provides two kind of methods. The "Register" method, covered in this article, is used by the client to register its own Uri with the service. In this method the Uri should be persisted somewhere - together with the date and time of registration for lifetime purposes - ensuring that this list is rapidly accessible and consistent. It is infact the list of client that should be notified when something occurs. In my example I simple manage a in-memory list but is it clear the a stop of the service will cause a loss of connectivity for all the clients.
1:
2: [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
3: public class NotificationService : INotificationService
4: {
5: private ConcurrentBag<Uri> Clients { get; set; }
6:
7: public NotificationService()
8: {
9: this.Clients = new ConcurrentBag<Uri>();
10: }
11:
12: public void Register(Uri uri)
13: {
14: if (!this.Clients.Contains(uri))
15: {
16: this.Clients.Add(uri);
17: Debug.WriteLine("Added => {0}", uri);
18: }
19: else
20: {
21: Debug.WriteLine("Already exists => {0}", uri);
22: }
23: }
24: }
The WCF service is declared "InstanceContextMode.Single" and "ConcurrencyMode.Multiple" just to ensure that the in-memory list survive across different calls. I also use a ConcurrentBag<T> to ensure that multiple access to the list are managed in a thread-safe way. From the previous explanation it is clear that the service has to authenticate itself to the WNS and get a token before to start to notify. This operation is made one time when the service is created, so I perform the authentication in the constructor.
1: private static readonly Uri LoginUri = new Uri("https://login.live.com/accesstoken.srf");
2: private const string BodyFormat = "grant_type=client_credentials&client_id={0}&client_secret={1}&scope=notify.windows.com";
3: private const string SID = "YOUR-OWN-SID";
4: private const string SecretKey = "YOUR-OWN-SECRET-KEY";
5:
6: private AuthenticationToken Token { get; set; }
7:
8: public NotificationService()
9: {
10: this.Clients = new ConcurrentBag<Uri>();
11: this.Token = this.Authenticate();
12: }
13:
14: private AuthenticationToken Authenticate()
15: {
16: var urlEncodedSecret = HttpUtility.UrlEncode(SecretKey);
17: var urlEncodedSid = HttpUtility.UrlEncode(SID);
18:
19: var body = String.Format(BodyFormat, urlEncodedSid, urlEncodedSecret);
20:
21: string response;
22:
23: using (var client = new WebClient())
24: {
25: client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
26: response = client.UploadString(LoginUri, body);
27: }
28:
29: return DeserializeResponse(response);
30: }
31:
32: private AuthenticationToken DeserializeResponse(string jsonString)
33: {
34: using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString)))
35: {
36: var ser = new DataContractJsonSerializer(typeof(AuthenticationToken));
37: var oAuthToken = (AuthenticationToken)ser.ReadObject(ms);
38: return oAuthToken;
39: }
40: }
41:
42: [DataContract]
43: private class AuthenticationToken
44: {
45: [DataMember(Name = "access_token")]
46: public string AccessToken { get; set; }
47: [DataMember(Name = "token_type")]
48: public string TokenType { get; set; }
49: }
The "Authenticate" method does all the work. It take the SID and the SecretKey - you can get these keys from the Windows Store dashboard when you register a name for an application, also for debug purposes - and send them to the Uri uset for the login under live Id. (https://login.live.com/accesstoken.srf). This service answer with a JSON message that is deserialized in a instance of AuthenticationToken class using the DataContractJsonSerializer. The sample code only handles the positive case but you should check for errors.
Moving to the device
Now that the service has been setup, it's time to move to the device code and accomplish authentication and registration tasks. After this task all the required relations have been established and the service can start to notify. Differently from the Windows Phone, the process of establishing a channel from the client to the WNS and communicating the handle to the cloud service is really straightforward.
1: private async void RegisterButton_Click(object sender, RoutedEventArgs e)
2: {
3: PushNotificationChannel channel = null;
4:
5: try
6: {
7: channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
8:
9: NotificationServiceClient client = new NotificationServiceClient();
10: await client.RegisterAsync(new Uri(channel.Uri));
11: }
12: catch (Exception ex)
13: {
14: MessageDialog dialog = new MessageDialog(ex.Message);
15: dialog.ShowAsync();
16: }
17: }
In these few lines all the process is completed. First of all with the PushNotificationChannelManager it is opened a channel to the WNS. When successfully opened, this channel returns an instance that contains the Uri used by the cloud service to communicate. It is in the channel.Uri property. Then, using the NotificationServiceClient created by Visual Studio to wrap the WCF service I've previously explained, I send the Uri to the Register method and it adds the client to the list of the notifications. If this few steps have completed without errors all the work is done and the server can start to notify directly to the Tiles, with badges (you already know the terms "tile" and "badge" given that I explained them previously) or with Toast messages that are shown over running apps. The server and the client can also agree on a raw notification for special purposes. But these will be the topics of the next part.