Note: Code here applies to CTP – I have not yet updated for the RTM.
This post provides the code required to send notifications through the Microsoft Push Notifications Service to a Windows Phone 7 device. I have put this together for the purpose of re-use across projects. Receiving and processing the notification on Windows Phone 7 will be the subject of a later post but I have some screenshots of the result here.
I will keep this breif as you are likely already aware, but for those of you who are not there are three types of notifications you can send to a Windows Phone 7 device:
- Tile – A tile is a visual, dynamic representation of application specific state within the quick launch area of the phone’s start experience
- Toast – Displays as an overlay onto the user’s current screen, a bit like an outlook email notification popup that you can press that launches you into the application.
- Raw – provides the ability for your application service to push data to the application while it is in the foreground without the need for your application to poll the appplication service.
So as a teaser – what do these look like in action:
So what exactly are we building a RawNotification, TileNotification and ToastNotification class with a Send method that returns a parsed NotificationResponse – here it is:
- Once you finish implementing this with the code provided in the post you will be able to send a notification as simply as follows:
ToastNotification toast = new ToastNotification("Wanted Toast", "But forgot the bread ", new Uri(txtUri.Text), Guid.NewGuid(), 2, null); ThreadPool.QueueUserWorkItem((o) => toast.Send(ProcessResponse));
TileNotification tile = new TileNotification(@"/Images/Background1.png", 2, "E = mc^2", new Uri(txtUri.Text), Guid.NewGuid(), 1, null); ThreadPool.QueueUserWorkItem((o) => tile.Send(ProcessResponse));
XmlDocument rawData = new XmlDocument(); string rawContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n\r\n" + "<root>\r\n" + "<foo>c</foo>\r\n" + "<bar>3000000</bar>\r\n" + "</root>"; rawData.LoadXml(rawContent); RawNotification raw = new RawNotification(rawData, new Uri(txtUri.Text), Guid.NewGuid(), 3, null); ThreadPool.QueueUserWorkItem((o) => raw.Send(ProcessResponse));
So now you can see how easy it is to use, how is the code for sending the notifications implemented:
NotificationBase.cs
using System; using System.IO; using System.Net; using System.Security.Cryptography.X509Certificates; namespace NickHarris.Net.Notification { public enum NotificationType { Token, Toast, Raw }; public abstract class NotificationBase { private Uri _URI; private Guid? _UUID; private int? _priority; private X509Certificate _certificate; private NotificationType _target; private byte[] FormattedNotificationMessage { get; set; } public NotificationBase(Uri uri, Guid? uuid, int? priority, X509Certificate certificate, NotificationType target) { _URI = uri; _UUID = uuid; _priority = priority; _certificate = certificate; _target = target; } protected abstract Byte[] GetTemplateFormatted(); protected abstract bool IsPriorityValid(int priority); protected HttpWebRequest CreateRequest(Uri uri, Guid? uuid, int? priority, X509Certificate certificate, NotificationType target) { if (priority.HasValue && !IsPriorityValid(priority.Value)) throw new ArgumentOutOfRangeException("Priority {0} is not valid for this notification type", priority.ToString()); // The URI that the Push Notification service returns to the Push Client when creating a notification channel. HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest; // HTTP POST is the only allowed method to send the notification. request.Method = WebRequestMethods.Http.Post; request.ContentType = "text/xml; charset=utf-8"; FormattedNotificationMessage = GetTemplateFormatted(); request.ContentLength = FormattedNotificationMessage.Length; request.Headers = GetHeaders(uuid, priority, target); // If the cloud service sending this request is authenticated, it needs to send its certificate. // Otherwise, this step is not needed. if (certificate != null) request.ClientCertificates.Add(certificate); return request; } protected WebHeaderCollection GetHeaders(Guid? uuid, int? priority, NotificationType target) { WebHeaderCollection headers = new WebHeaderCollection(); // The optional custom header X-MessageID uniquely identifies a notification message. If it is present, the same value is returned. // in the notification response. It must be a string that contains a UUID. if (uuid.HasValue) headers.Add("X-MessageID", uuid.Value.ToString()); if (target != NotificationType.Raw) headers.Add("X-WindowsPhone-Target", target.ToString().ToLower()); // The optional custom header X-NotificationClass sets the notification class for this notification request. The valid value range is // from 1-31. If this value is not present, the server defaults to 2 if (priority.HasValue) headers.Add("X-NotificationClass", priority.Value.ToString()); return headers; } public void Send(Action<NotificationResponse> callback) { HttpWebRequest request = CreateRequest(_URI, _UUID, _priority, _certificate, _target); request.BeginGetRequestStream((x) => { using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(FormattedNotificationMessage, 0, FormattedNotificationMessage.Length); requestStream.Close(); } //Get Async response from push notification service request.BeginGetResponse((asyncResult) => { using (WebResponse notifyResponse = request.EndGetResponse(asyncResult)) { //callback to notify caller of result callback(new NotificationResponse((HttpWebResponse)notifyResponse)); } }, null); }, null); } } }
RawNotification.cs
using System; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Xml; namespace NickHarris.Net.Notification { public class RawNotification:NotificationBase { public XmlDocument Payload { get; set; } public RawNotification(XmlDocument payload, Uri subscriptionUri, Guid? uuid, int? priority, X509Certificate certificate) : base(subscriptionUri, uuid, priority, certificate, NotificationType.Raw) { Payload = payload; } protected override byte[] GetTemplateFormatted() { return Encoding.UTF8.GetBytes(Payload.OuterXml); } protected override bool IsPriorityValid(int priority) { return (priority > 2 && priority < 11) //3-10 – Real Time. The raw notification is delivered as soon as possible. || (priority > 12 && priority < 21) //13-20 – Priority. The raw notification is delivered at a predefined timeout. || (priority > 22 && priority < 32); //23-31 – Regular. The raw notification is delivered at a predefined timeout which is greater than the Priority batching interval. } } }
TileNotification.cs
using System; using System.Security.Cryptography.X509Certificates; using System.Text; namespace NickHarris.Net.Notification { public class TileNotification : NotificationBase { public string BackgroundImage { get; set; } public int Count { get; set; } public string Title { get; set; } public TileNotification(string backgroundImage, int count, string title, Uri subscriptionUri, Guid? uuid, int? priority, X509Certificate certificate) : base(subscriptionUri, uuid, priority, certificate, NotificationType.Token) { BackgroundImage = backgroundImage; Count = count; Title = title; } protected override Byte[] GetTemplateFormatted() { string tileMessage = "X-WindowsPhone-Target: token\r\n\r\n" + //Note: I have left this in line so you can see the format. Would be better if you add it to a Resource file "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<wp:Notification xmlns:wp=\"WPNotification\">" + "<wp:Token>" + "<wp:Img>{0}</wp:Img>" + "<wp:Count>{1}</wp:Count>" + "<wp:Title>{2}</wp:Title>" + "</wp:Token> " + "</wp:Notification>"; //return new UTF8Encoding().GetBytes(string.Format(Properties.Resources.ToastNotificationTemplate, Environment.NewLine, Environment.NewLine, BackgroundImage, Count, Title)); return new UTF8Encoding().GetBytes(string.Format(tileMessage,BackgroundImage, Count, Title)); } protected override bool IsPriorityValid(int priority) { return priority == 1 // 1 – Real Time. The tile notification is delivered as soon as possible. || priority == 11 //11 – Priority. The tile notification is delivered at a predefined timeout. || priority == 21; //21 – Regular. The tile notification is delivered at a predefined timeout which is greater than the Priority batching interval. } } }
ToastNotification.cs
using System; using System.Security.Cryptography.X509Certificates; using System.Text; namespace NickHarris.Net.Notification { public class ToastNotification: NotificationBase { public string TitleOne { get; set; } public string TitleTwo { get; set; } public ToastNotification(string titleOne, string titleTwo, Uri subscriptionUri, Guid? uuid, int? priority, X509Certificate certificate) : base(subscriptionUri, uuid, priority, certificate, NotificationType.Toast) { TitleOne = titleOne; TitleTwo = titleTwo; } protected override Byte[] GetTemplateFormatted() { string toastTemplate = "X-WindowsPhone-Target: TOAST\r\n\r\n" + //Note: I have left this in line so you can see the format. Would be better if you add it to a Resource file "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<wp:Notification xmlns:wp=\"WPNotification\">" + "<wp:Toast>" + "<wp:Text1>{0}</wp:Text1>" + "<wp:Text2>{1}</wp:Text2>" + "</wp:Toast>" + "</wp:Notification>"; return new UTF8Encoding().GetBytes(string.Format(toastTemplate, TitleOne, TitleTwo)); //return new UTF8Encoding().GetBytes(string.Format(Properties.Resources.ToastNotificationTemplate, Environment.NewLine, Environment.NewLine, TitleOne, TitleTwo)); } protected override bool IsPriorityValid(int priority) { return priority == 2 //2 – Real Time. The toast notification is delivered as soon as possible. || priority == 12 //12 – Priority. The toast notification is delivered at a predefined timeout. || priority == 22; //22 – Regular. The toast notification is delivered at a predefined timeout which is greater than the Priority batching interval. } } }
NotificationResponse.cs
using System; using System.Net; namespace NickHarris.Net.Notification { public enum NotificationStatus { None = 0, Received, QueueFull, Dropped, Unknown }; //N/A mapped to None, Unknown -> unknown will likely occur with updates to the CTP public enum DeviceConnectionStatus { None = 0, Connected, TemporarilyDisconnected, Inactive, Unknown }; public enum SubscriptionStatus { None = 0, Active, Expired, Unknown }; public struct NotificationResponse { private string _messageID; //keep as string not everyone will be using guids private NotificationStatus _notificationStatus; private SubscriptionStatus _subscriptionStatus; private DeviceConnectionStatus _deviceConnectionStatus; private HttpStatusCode _httpStatusCode; public NotificationResponse(HttpWebResponse response) { _messageID = response.Headers["X-MessageID"]; _notificationStatus = GetNotificationStatus(response.Headers["X-NotificationStatus"]); _subscriptionStatus = GetSubscriptionStatus(response.Headers["X-SubscriptionStatus"]); _deviceConnectionStatus = GetDeviceConnectionStatus(response.Headers["X-DeviceConnectionStatus"]); _httpStatusCode = response.StatusCode; } public HttpStatusCode HttpStatusCode { get { return _httpStatusCode; } private set { _httpStatusCode = value; } } public DeviceConnectionStatus DeviceConnectionStatus { get { return _deviceConnectionStatus; } private set { _deviceConnectionStatus = value; } } public SubscriptionStatus SubscriptionStatus { get { return _subscriptionStatus; } private set { _subscriptionStatus = value; } } public NotificationStatus NotificationStatus { get { return _notificationStatus; } private set { _notificationStatus = value; } } public string MessageID { get { return _messageID; } private set { _messageID = value; } } private static NotificationStatus GetNotificationStatus(string notificationStatus) { NotificationStatus status = NotificationStatus.Unknown; if (notificationStatus.ToUpper() == "N/A") status = NotificationStatus.None; else if (Enum.IsDefined(typeof(NotificationStatus), notificationStatus)) status = (NotificationStatus)Enum.Parse(typeof(NotificationStatus), notificationStatus, true); else {//leave as unknown } return status; } private static DeviceConnectionStatus GetDeviceConnectionStatus(string deviceConnectionStatus) { DeviceConnectionStatus status = DeviceConnectionStatus.Unknown; deviceConnectionStatus = deviceConnectionStatus.Replace(" ", string.Empty); if (deviceConnectionStatus.ToUpper() == "N/A") status = DeviceConnectionStatus.None; else if (Enum.IsDefined(typeof(DeviceConnectionStatus), deviceConnectionStatus)) status = (DeviceConnectionStatus)Enum.Parse(typeof(DeviceConnectionStatus), deviceConnectionStatus, true); else {//leave as unknown } return status; } private static SubscriptionStatus GetSubscriptionStatus(string subscriptionStatus) { SubscriptionStatus status = SubscriptionStatus.Unknown; if (subscriptionStatus.ToUpper() == "N/A") status = SubscriptionStatus.None; else if (Enum.IsDefined(typeof(SubscriptionStatus), subscriptionStatus)) status = (SubscriptionStatus)Enum.Parse(typeof(SubscriptionStatus), subscriptionStatus, true); else {//leave as unknown } return status; } } }
So now for the disclaimer – Main thing is – it works (apart from remote tile notifications – currently an issue with the CTP as far as I can tell). This version of the code was created against the requirements of the April CTP so I expect the message format to change although if you put it into your project resource file as the //comments above dictate you should be able to change this as the developer toolkit evolves.
I will to keep this blog up to date with fixes for any changes as future CTPs are released or bugs I find as I dig deeper so make sure you subscribe to my RSS feed.
Enjoy the code and the nights you will save not having to write it
Nick