PushNotificationSender.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. //-----------------------------------------------------------------------------
  2. // PushNotificationSender.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Text;
  9. using System.Net;
  10. using System.IO;
  11. namespace PushNotificationSender
  12. {
  13. /// <summary>
  14. /// A utility class for sending the three different types of push notifications.
  15. /// </summary>
  16. public class PushNotificationSender
  17. {
  18. public enum NotificationType
  19. {
  20. Tile = 1,
  21. Toast = 2,
  22. Raw = 3
  23. }
  24. public delegate void SendCompletedEventHandler(PushNotificationCallbackArgs args);
  25. public event SendCompletedEventHandler NotificationSendCompleted;
  26. public const string MESSAGE_ID_HEADER = "X-MessageID";
  27. public const string NOTIFICATION_CLASS_HEADER = "X-NotificationClass";
  28. public const string NOTIFICATION_STATUS_HEADER = "X-NotificationStatus";
  29. public const string DEVICE_CONNECTION_STATUS_HEADER = "X-DeviceConnectionStatus";
  30. public const string SUBSCRIPTION_STATUS_HEADER = "X-SubscriptionStatus";
  31. public const string WINDOWSPHONE_TARGET_HEADER = "X-WindowsPhone-Target";
  32. public const int MAX_PAYLOAD_LENGTH = 1024;
  33. /// <summary>
  34. /// Sends a raw notification, which is just a byte payload.
  35. /// </summary>
  36. public void SendRawNotification(Uri deviceUri, byte[] payload)
  37. {
  38. SendNotificationByType(deviceUri, payload, NotificationType.Raw);
  39. }
  40. /// <summary>
  41. /// Sends a tile notification, which is a title, a count, and an image URI.
  42. /// </summary>
  43. public void SendTileNotification(Uri deviceUri, string title, int count, string backgroundImage)
  44. {
  45. // Malformed push notifications cause exceptions to be thrown on the receiving end
  46. // so make sure we have valid data to start with.
  47. if (string.IsNullOrEmpty(title))
  48. {
  49. throw new InvalidOperationException("Tile notifications require title text");
  50. }
  51. // Set up the XML
  52. string msg =
  53. "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
  54. "<wp:Notification xmlns:wp=\"WPNotification\">" +
  55. "<wp:Tile>";
  56. if (!string.IsNullOrEmpty(backgroundImage))
  57. {
  58. msg += "<wp:BackgroundImage>" + backgroundImage + "</wp:BackgroundImage>";
  59. }
  60. msg +=
  61. "<wp:Count>" + count.ToString() + "</wp:Count>" +
  62. "<wp:Title>" + title + "</wp:Title>" +
  63. "</wp:Tile>" +
  64. "</wp:Notification>";
  65. byte[] payload = new UTF8Encoding().GetBytes(msg);
  66. SendNotificationByType(deviceUri, payload, NotificationType.Tile);
  67. }
  68. /// <summary>
  69. /// Sends a toast notification, which is two lines of text.
  70. /// </summary>
  71. public void SendToastNotification(Uri deviceUri, string text1, string text2)
  72. {
  73. // Malformed push notifications cause exceptions to be thrown on the receiving end
  74. // so make sure we have valid data to start with.
  75. if (string.IsNullOrEmpty(text1) && string.IsNullOrEmpty(text2))
  76. {
  77. throw new InvalidOperationException("toast notifications must have at least 1 valid string");
  78. }
  79. // Set up the XML
  80. string msg =
  81. "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
  82. "<wp:Notification xmlns:wp=\"WPNotification\">" +
  83. "<wp:Toast>" +
  84. "<wp:Text1>" + text1 + "</wp:Text1>" +
  85. "<wp:Text2>" + text2 + "</wp:Text2>" +
  86. "</wp:Toast>" +
  87. "</wp:Notification>";
  88. byte[] payload = new UTF8Encoding().GetBytes(msg);
  89. SendNotificationByType(deviceUri, payload, NotificationType.Toast);
  90. }
  91. /// <summary>
  92. /// helper function to set up the request headers based on type and send the notification payload.
  93. /// </summary>
  94. private void SendNotificationByType(Uri channelUri, byte[] payload, NotificationType notificationType)
  95. {
  96. // Check the length of the payload and reject it if too long.
  97. if (payload.Length > MAX_PAYLOAD_LENGTH)
  98. throw new ArgumentOutOfRangeException("Payload is too long. Maximum payload size shouldn't exceed " + MAX_PAYLOAD_LENGTH.ToString() + " bytes");
  99. try
  100. {
  101. // Create and initialize the request object.
  102. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(channelUri);
  103. request.Method = WebRequestMethods.Http.Post;
  104. request.ContentLength = payload.Length;
  105. request.Headers[MESSAGE_ID_HEADER] = Guid.NewGuid().ToString();
  106. // Each type of push notification uses a different code in its X-NotificationClass
  107. // header to specify its delivery priority. The three priorities are:
  108. // Realtime. The notification is delivered as soon as possible.
  109. // Priority. The notification is delivered within 450 seconds.
  110. // Regular. The notification is delivered within 900 seconds.
  111. // Realtime Priority Regular
  112. // Raw 3-10 13-20 23-31
  113. // Tile 1 11 21
  114. // Toast 2 12 22
  115. switch (notificationType)
  116. {
  117. case NotificationType.Tile:
  118. // the notification type for a tile notification is "token".
  119. request.Headers[WINDOWSPHONE_TARGET_HEADER] = "token";
  120. request.ContentType = "text/xml";
  121. // Request real-time delivery for tile notifications.
  122. request.Headers[NOTIFICATION_CLASS_HEADER] = "1";
  123. break;
  124. case NotificationType.Toast:
  125. request.Headers[WINDOWSPHONE_TARGET_HEADER] = "toast";
  126. request.ContentType = "text/xml";
  127. // Request real-time delivery for toast notifications.
  128. request.Headers[NOTIFICATION_CLASS_HEADER] = "2";
  129. break;
  130. case NotificationType.Raw:
  131. // Request real-time delivery for raw notifications.
  132. request.Headers[NOTIFICATION_CLASS_HEADER] = "3";
  133. break;
  134. default:
  135. throw new ArgumentException("Unknown notification type", "notificationType");
  136. }
  137. Stream requestStream = request.GetRequestStream();
  138. requestStream.Write(payload, 0, payload.Length);
  139. requestStream.Close();
  140. HttpWebResponse response = request.GetResponse() as HttpWebResponse;
  141. if (NotificationSendCompleted != null && response != null)
  142. {
  143. PushNotificationCallbackArgs args = new PushNotificationCallbackArgs(notificationType, (HttpWebResponse)response);
  144. NotificationSendCompleted(args);
  145. }
  146. }
  147. catch (WebException ex)
  148. {
  149. // Notify the caller on exception as well.
  150. if (NotificationSendCompleted != null)
  151. {
  152. if (null != ex.Response)
  153. {
  154. PushNotificationCallbackArgs args = new PushNotificationCallbackArgs(notificationType, (HttpWebResponse)ex.Response);
  155. NotificationSendCompleted(args);
  156. }
  157. }
  158. }
  159. }
  160. }
  161. }