DarksideCookie

Come to the dark side...we have cookies!

Windows Phone 7 Push Notifications Revisited

A couple of weeks ago…ehh…well…let’s at least call it a couple of weeks ago (it might have been longer to be honest) I posted a blog post about using push notification on the Windows Phone 7 platform. It included more or less everything you needed to start sending push notifications on the pre-beta version of the platform.

However, about a week ago (once again…+- a couple of days) Microsoft released the new beta version of the developer tools as well as the emulator. And I assume they also pushed I to the lucky bastards that already have phones.

In this new beta, they have changed the API for the notifications, and hence destroyed my previous code. So I have updated my sample to use the new tools and the new APIs. So here I go again…

The rules of the game are still the same. The phone application opens a notification channel using the HttpNotificationChannel class. Once that is done, it gets a unique Uri back. This Uri is then passed to some server somewhere to enable it to send notifications to the phone. The server then uses HTTP POSTs to send messages to Microsoft’s push notification service, which in turn push the notification to the phone.

In my version, there is one more piece in the puzzle - the sender. The sender application in this case is an WPF application that sends notifications by calling a webservice on the server, which then POSTs the message to Microsoft.

So, from that point, very little has changed. From the phone API point, quite a lot has actually changed. So I will start in that end, and look at the phone application…

First of all, in the previous version, you had to remember to set a value in the WMAppManifest.xml file. Otherwise you would get some odd error. You also needed to wait for up to 2 minutes for the networking stuff to start working. If you used it before the “right” time, it would throw an error where an error was not expected, so you had to handle that situation.

Those issues are now fixed, and the code becomes a bit easier to read. It also becomes a bit easier to read based on the fact that they have removed a bit of the functionality of the HttpNotificationChannel class. In the new version, it is not possible to get notified when a tile update arrives while the application is running. Now, you will only get notified when a raw Http notification or a toast is received. And on top of that, some of the method names and events and so on has been changed.

The XAML for the phone application is unchanged from the previous post, except for the removal of the tile update information. So I will leave that out of it. Just as I will leave out the ViewModel, as it is also unchanged. This is the good thing about separating out functionality in “services”. When the notification API changes, I just change the service implementation and the UI and VM can stay untouched.

The NotificationService on the other hand has changed quite a lot, so I will go through it thoroughly.

It starts out with a simple constructor with a single parameter, the name to use for the channel. It stores this in a variable and then goes on to either create or retrieve a unique ID for the application. The unique ID is used to identify the application when registering the channel Uri. It is a very crude way to implement it, but it basically creates a new Guid and stores it in isolated storage. If it is there the next time, it gets it from storage instead of creating a new one…


public class NotificationService
{
private string CHANNEL_NAME;
Guid _appId;

public NotificationService(string channelName)
{
CHANNEL_NAME = channelName;
if (IsolatedStorageSettings.ApplicationSettings.Contains("AppID"))
{
_appId = (Guid)IsolatedStorageSettings.ApplicationSettings["AppID"];
}
else
{
_appId = Guid.NewGuid();
IsolatedStorageSettings.ApplicationSettings["AppID"] = _appId;
}

SetUpChannel();
}
...
}

 

Next, as you might have noticed in the snippet above, it calls the SetUpChannel() method. This method starts out by trying to find an existing channel with the same name. If there isn’t one, which there won’t be on the first execution, it creates a new one. Otherwise, it registers eventhandlers to the notification events.

The creation of a new channel is not complicated at all. You just create a new HttpNotificationChannel instance passing in the name you whish to use for the channel. Next, you attach handlers to the ChannelUriUpdated and ErrorOccurred events. And finally you call the Open() method. The Open() method is asynchronously and notifies its completion by raising the ChannelUriUpdated or ErrorOccurred event event depending on the outcome of the method call.

void SetUpChannel()
{
_channel = HttpNotificationChannel.Find(CHANNEL_NAME);

if (_channel == null)
{
_channel = new HttpNotificationChannel(CHANNEL_NAME);
_channel.ChannelUriUpdated += OnChannelUriUpdated;
_channel.ErrorOccurred += OnErrorOccurred;
_channel.Open();
}
else
{
RegisterForNotifications();
}
}

The RegisterForNotifications() method that is called if there is already an open channel, is called later on in the code if there isn’t one. So you will see that in just a little while…

In the OnChannelUriUpdated() method, I start off by detaching the eventhandlers from both the ChannelUriUpdated and ErrorOccurred events. After that, I do an odd little thing. I once again try to find a channel with the specified name by calling HttpNotificationChannel.Find(). I don’t know if it is only me, but in my environment, the channel reference that I have and that I called Open() on, doesn’t actually work. It throws an exception saying that the channel isn’t open. If I “re-find” it, the returned reference seems to work though.

I would love it if someone could verify this for me on another system…

Next, I check if a tile is bound before binding one if necessary. I think that the ability to so if a tile has already been bound is new to this release as well. I didn’t actually check before I upgraded…sorry!

The registration of the tile has also changed. You can still call it without any parameters, but you can’t call it passing in a tile object anymore. Instead, you can now pass in a collection of Uris. These Uri objects, define base Uris that are accepted when sending a tile update.

In this release, it actually supports tile images that are not local. Local tile images are however still supported as well. But if you do not pass in a collection of base Uris, you will not be able to use this feature. You must at least specify the host of the server that will host the files.

This makes for some very interesting options around auto generating tiles on the server. Something I am likely to come back to in a blog post pretty soon.

When a tile has been registered, it goes ahead and checks if toasts are bound. If not, it binds the application for toast notifications. And finally it calls the RegisterForNotifications(), just as was done previously if there was an existing channel.

void OnChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
_channel.ErrorOccurred -= OnErrorOccurred;
_channel.ChannelUriUpdated -= OnChannelUriUpdated;

_channel = HttpNotificationChannel.Find(CHANNEL_NAME);

if (!_channel.IsShellTileBound)
{
Collection<Uri> uris = new Collection<Uri>();
uris.Add(new Uri("http://chris.59north.com/"));
_channel.BindToShellTile(uris);
}

if (!_channel.IsShellToastBound)
_channel.BindToShellToast();

RegisterForNotifications();
}

 

The RegisterForNotifications() method starts out by registering the application Uri with the server using a webservice call, and then goes on to attach handlers to the ShellToastNotificationReceived, HttpNotificationReceived and ErrorOccurred events.

void RegisterForNotifications()
{
RegisterWithSubscriptionService();
_channel.ShellToastNotificationReceived += OnShellToastNotificationReceived;
_channel.HttpNotificationReceived += OnHttpNotificationReceived;
_channel.ErrorOccurred += OnErrorOccurred;
}

The handlers for these events are quite simple. They basically take the received notifications, and push them to any interested listener by raising events.

void OnErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
{
Status = NotificationStatus.Error;
OnErrorOccurred(e.Message);
}
void OnHttpNotificationReceived(object sender, HttpNotificationEventArgs e)
{
StreamReader sr = new StreamReader(e.Notification.Body);
OnRawMessageReceived(sr.ReadToEnd());
sr.Close();
}
void OnShellToastNotificationReceived(object sender, NotificationEventArgs e)
{
ToastData td = new ToastData(e.Collection["wp:Text1"], e.Collection["wp:Text2"]);
OnToastReceived(td);
}protected virtual void OnErrorOccurred(string payload)
{
if (ErrorOccurred != null)
{
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
{
ErrorOccurred(this, new PayloadEventArgs<string>(payload));
});
}
}
public event EventHandler<PayloadEventArgs<string>> ErrorOccurred;

protected virtual void OnToastReceived(ToastData payload)
{
if (ToastReceived != null)
{
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
{
ToastReceived(this, new PayloadEventArgs<ToastData>(payload));
});
}
}
public event EventHandler<PayloadEventArgs<ToastData>> ToastReceived;

protected virtual void OnRawMessageReceived(string message)
{
if (RawMessageReceived != null)
{
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
{
RawMessageReceived(this, new PayloadEventArgs<string>(message));
});
}
}
public event EventHandler<PayloadEventArgs<string>> RawMessageReceived;

 

As you can see in the OnXXX methods, I use the System.Windows.Deployment.Current.Dispatcher property to get thread safe access to the current Dispatcher and use it when raising the events. This makes it easy for the VM to change properties during the events.

That’s it for the phone. A few changes in the API, but a bit less code…

Next up is the webservice responsible for sending the messages. It is also responsible for keeping track of the client applications Uris. But the later hasn’t changed since the last post, so I am leaving that out…

The ISubscriptionService interface has changed a little. Each of the SendXXX() methods now return an array of SendStatus. SendStatus is an enumeration that contains the different results that you might get from the push notification service. It is a somewhat simplified list, but should cover most common scenarios. More information about the statuses are available at the bottom of the page here.

public enum SendStatus
{
TransmissionError,
Received,
ReceivedTempDisconnected,
QueueFull,
Suppressed,
BadRequest,
Unauthorized,
InvalidSubscription,
DeviceInactive,
PushNotificationServiceUnavailable,
Throttled
}

The method that is responsible for POSTing the messages to the push notification service has also changed a bit. It 4 parameters and returns a SendStatus. The 4 parameters are the Uri to the client, the message to send as a byte array, an integer representation of the notification class and a string based target.

The method creates a new HttpWebRequest based on the client Uri. It then sets the X-NotificationClass header and X-MessageID header and finally the X-WindowsPhone-Target header if a target has been supplied. It then sets the method to POST, the content type to “text/xml; charset=utf-8” and finally the content length. After all the important properties have been set, it sends off the message using the request stream.

As soon as the message has been sent, I get the response and check the 3 response headers that Microsoft pass back. Using these the values of these headers, I then return a corresponding SendStatus value. If an error is thrown when getting the header, I check the type of exception and status code to determine the cause of the error. This cause is then reflected in the returned status.

private SendStatus PostMessage(Uri uri, byte[] msg, int notificationClass, string target)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
req.Headers.Add("X-NotificationClass", notificationClass.ToString());
req.Headers.Add("X-MessageID", Guid.NewGuid().ToString());
if (target.Length > 0)
req.Headers["X-WindowsPhone-Target"] = target;
req.Method = "POST";
req.ContentType = "text/xml; charset=utf-8";
req.ContentLength = msg.Length;

SendStatus ret;
using (Stream requestStream = req.GetRequestStream())
{
requestStream.Write(msg, 0, msg.Length);
}
try
{
HttpWebResponse response = (HttpWebResponse)req.GetResponse();

string notificationStatus = response.Headers["X-NotificationStatus"];
string subscriptStatus = response.Headers["X-SubscriptionStatus"];
string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];

ret = SendStatus.Received;
if (deviceConnectionStatus == "TempDisconnected")
ret = SendStatus.ReceivedTempDisconnected;
if (notificationStatus == "QueueFull")
ret = SendStatus.QueueFull;
if (notificationStatus == "Suppressed")
ret = SendStatus.Suppressed;
}
catch (WebException ex)
{
ret = SendStatus.TransmissionError;
if (ex.Status == WebExceptionStatus.ProtocolError)
{
switch (((HttpWebResponse)ex.Response).StatusCode)
{
case HttpStatusCode.BadRequest:
ret = SendStatus.BadRequest;
break;
case HttpStatusCode.Unauthorized:
ret = SendStatus.Unauthorized;
break;
case HttpStatusCode.NotFound:
ret = SendStatus.InvalidSubscription;
break;
case HttpStatusCode.PreconditionFailed:
ret = SendStatus.DeviceInactive;
break;
case HttpStatusCode.ServiceUnavailable:
ret = SendStatus.PushNotificationServiceUnavailable;
break;
//case HttpStatusCode.UserAccessDenied: // HTTP 530
// ret = SendStatus.Throttled;
// break;
}
}
}
return ret;
}

Unfortunately, I couldn’t find a way to check for a HTTP 530, which is what you get when the push notification service starts throttling your notifications…

The PostMessage() method is then used by the three SendXXX() methods. Each of these methods create the message the whish to send and then pass it to the PostMessage() method. The XML message used for tile notifications have changed a little, but otherwise everything is sort of the same… It now looks like this

<?xml version=\"1.0\" encoding=\"utf-8\"?>
<wp:Notification xmlns:wp=\"WPNotification\">
<wp:Tile>
<wp:Title>{0}</wp:Title>
<wp:Count>{1}</wp:Count>
<wp:BackgroundImage>{2}</wp:BackgroundImage>
</wp:Tile>
</wp:Notification>

The SendXXX() methods also saves the SendStatus for each of the recipients and pass an array of these back to the caller

public SendStatus[] SendToast(string text1, string text2)
{
List<SendStatus> statuses = new List<SendStatus>();
string message = string.Format(toastMessage, text1, text2);
byte[] msg = System.Text.Encoding.UTF8.GetBytes(message);
foreach (var uri in _clients.Values)
{
statuses.Add(PostMessage(uri, msg, 2, "toast"));
}
return statuses.ToArray();
}
public SendStatus[] SendTileUpdate(string title, int count, string imageUrl)
{
List<SendStatus> statuses = new List<SendStatus>();
string message = string.Format(tileMessage, title, count, imageUrl);
byte[] msg = System.Text.Encoding.UTF8.GetBytes(message);
foreach (var uri in _clients.Values)
{
statuses.Add(PostMessage(uri, msg, 1, "toast"));
}
return statuses.ToArray();
}
public SendStatus[] SendRawMessage(string message)
{
List<SendStatus> statuses = new List<SendStatus>();
byte[] msg = System.Text.Encoding.UTF8.GetBytes(message);
foreach (var uri in _clients.Values)
{
statuses.Add(PostMessage(uri, msg, 3, ""));
}
return statuses.ToArray();
}

That’s it for the web server. So now there is only one part left, the sender. My not so very awesome WPF application. Except for an updated webservice and a way to handle the returned statuses, it is more or less identical to the way it was before…or…well…I have also made some changes to the tile images that can be sent…

The click eventhandlers for the send buttons on the tabs, all do more or less the same. The take the values on the tab and pass them to the web service. They then take the returned statuses and pass them to a method that uses them to inform the user of the outcome. The NotifyResponse() method looks like this

private void NotifyResponse(SubscriptionService.SendStatus[] response)
{
Dictionary<SubscriptionService.SendStatus, int> statuses = new Dictionary<SubscriptionService.SendStatus, int>();
foreach (var item in response)
{
if (!statuses.ContainsKey(item))
{
statuses.Add(item, 0);
}
statuses[item]++;
}

StringBuilder sb = new StringBuilder("Send report\r\n\r\n");
sb.AppendLine(response.Length + " message(s) sent");
foreach (var item in statuses)
{
sb.Append("\r\n");
sb.AppendFormat("{0} {1} message(s)",item.Value, item.Key);
}

MessageBox.Show(sb.ToString());
}

As you can see, it uses a StringBuilder to build up a message which is then presented to the user using a MessageBox. Not beautiful, but it works…

The other change I mentioned regarding the tile images that can be sent, includes a couple of things. First of all, the relative Uris used for local images on the device, have now changed so they have to start with a slash (/). I have then also changed the way that they are presented, by changing the combo box values from just the Uri to “{name}  | {uri}”. So they look like this instead…

<Sys:String>Local Icon 1 | /Images/LocalTile1.png</Sys:String>
<Sys:String>Local Icon 2 | /Images/LocalTile2.png</Sys:String>
<Sys:String>Local Icon 3 | /Images/LocalTile3.png</Sys:String>
<Sys:String>Approved Web Tile | http://chris.59north.com/file.axd?file=2010%2f7%2fOnLineTile.jpg</Sys:String>
<Sys:String>Unapproved Web Tile | http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/1033.image_5F00_thumb_5F00_386776A0.png</Sys:String>

This is the ugly, poor mans way out, I know. I should have created some sweet objects that wrapped both values and then bound to those and so on…I didn’t so just let it go…and I didn’t use MVVM for the client either…it is a hacked up crap client to test some functionality…ok!? Smile

I have as you might have seen also added 2 non local images. One that is ok and comes from my website, a Uri that was registered when registering for tile notifications. But also one image that is not valid as it comes form the wrong Uri. And don’t ask me why I have the worlds longest image file from the Windows Team blog, I just happened to use that for some reason when testing something, and it stuck…

The eventhandler for the “send tile update button” has changed a bit as well. It parses the selected string and gets the Uri part only, and then passes it on…

private void SendTile_Click(object sender, RoutedEventArgs e)
{
SubscriptionService.SubscriptionServiceClient svc = new SubscriptionService.SubscriptionServiceClient();
string[] url = ((string)TileImage.SelectedItem).Split('|');
SubscriptionService.SendStatus[] response = svc.SendTileUpdate(TileTitle.Text, int.Parse(TileCount.Text), url[1].Trim());
NotifyResponse(response);
}

That is IT! That is all you need to know about sending tiles…or at least a lot of it…

Code is available usual: Demo.PushNotification.zip (96.34 kb)

And if you have any comments or questions or possibly can confirm my “re-finding” of the HttpNotificationChannel, just add a comment or drop me a line…

Oh…I have also set up the solution to start all 3 projects at once, in the correct order. So it should just be a matter of pressing F5…

[UPDATE]
New code sample uploaded on August 9 2010 due to some changes…

Posted: Jul 19 2010, 11:32 by ZeroKoll | Comments (7) |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Manage post: :)

Comments (7) -

Kevin United States said:

Thanks for the great example.  Question, have you ever gotten the tile notification to work on the emulator?  I never got it to work and I have tried from sample app to sample app.  None of them work.  The code is all similar.  Any idea?

# September 17 2010, 16:52

Chris New Zealand said:

Hi Kevin!
Yes, the emulator works fine when it comes to tile notifications. Try my code above. It works perfectly for me. Just remember to use the original emulator as the hacked one seems to have issues with tiles.
And also, ISA and other proxy servers sometimes block the request for a channel uri. Make sure that the ChannelUriUpdated event is raised. If not, it has probably been blocked along the way. It can take a little while though...
// Chris

# September 25 2010, 01:37

Talgat ALikparov Russia said:

Thank you, your example works fine. I can not add one more picture. I added a picture in the folder "Images" in the project Demo.PushNotification.ClientApp. Then added the line "Local Icon 4 | /Images/LocalTile4.png " in project "Demo.PushNotification.Sender. " Instead of my picture shows a blue background. In what may be the problem?

# February 17 2011, 06:57

ZeroKoll New Zealand said:

Hi Talgat!
Have you set the correct Build Action for the image?
// Chris

# February 17 2011, 07:07

abc India said:

What are the conditions in which channel uri changes.

If it changes when application not running how to handle

# September 12 2011, 13:55

ahmad United States said:

hello
how adding an object to TOAST alert?

# December 31 2011, 14:47

ZeroKoll Sweden said:

Hi Ahmad!
Could you please elaborate about your question? What are you trying to do?
// Chris

# January 04 2012, 14:51

Pingbacks and trackbacks (1)+

Comments are closed