| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Net;
- using System.Diagnostics;
- using System.Threading;
- using WindowsPhone.Recipes.Push.Messasges.Properties;
- namespace WindowsPhone.Recipes.Push.Messasges
- {
- /// <summary>
- /// Represents a base class for push notification messages.
- /// </summary>
- /// <remarks>
- /// This class members are thread safe.
- /// </remarks>
- public abstract class PushNotificationMessage
- {
- #region Constants
- /// <value>Push notification maximum message size including headers and payload.</value>
- protected const int MaxMessageSize = 1024;
- /// <summary>
- /// Well known push notification message web request headers.
- /// </summary>
- internal static class Headers
- {
- public const string MessageId = "X-MessageID";
- public const string BatchingInterval = "X-NotificationClass";
- public const string NotificationStatus = "X-NotificationStatus";
- public const string DeviceConnectionStatus = "X-DeviceConnectionStatus";
- public const string SubscriptionStatus = "X-SubscriptionStatus";
- public const string WindowsPhoneTarget = "X-WindowsPhone-Target";
- }
- #endregion
- #region Fields
- /// <value>Synchronizes payload manipulations.</value>
- private readonly object _sync = new object();
- /// <value>The payload raw bytes of this message.</value>
- private byte[] _payload;
- private MessageSendPriority _sendPriority;
- #endregion
- #region Properties
- /// <summary>
- /// Gets this message unique ID.
- /// </summary>
- public Guid Id { get; private set; }
- /// <summary>
- /// Gets or sets the send priority of this message in the MPNS.
- /// </summary>
- public MessageSendPriority SendPriority
- {
- get
- {
- return _sendPriority;
- }
- set
- {
- SafeSet(ref _sendPriority, value);
- }
- }
- /// <summary>
- /// Gets or sets the message payload.
- /// </summary>
- protected byte[] Payload
- {
- get
- {
- return _payload;
- }
- set
- {
- SafeSet(ref _payload, value);
- }
- }
- protected abstract int NotificationClassId
- {
- get;
- }
- /// <summary>
- /// Gets or sets the flag indicating that one of the message properties
- /// has changed, thus the payload should be rebuilt.
- /// </summary>
- private bool IsDirty { get; set; }
- #endregion
- #region Ctor
- /// <summary>
- /// Initializes a new instance of this type with <see cref="WindowsPhone.Recipes.Push.Messasges.MessageSendPriority.Normal"/> send priority.
- /// </summary>
- protected PushNotificationMessage(MessageSendPriority sendPriority = MessageSendPriority.Normal)
- {
- Id = Guid.NewGuid();
- SendPriority = sendPriority;
- IsDirty = true;
- }
- #endregion
- #region Operations
- /// <summary>
- /// Synchronously send this messasge to the destination address.
- /// </summary>
- /// <remarks>
- /// Note that properties of this instance may be changed by different threads while
- /// sending, but once the payload created, it won't be changed until the next send.
- /// </remarks>
- /// <param name="uri">Destination address uri.</param>
- /// <exception cref="ArgumentNullException">One of the arguments is null.</exception>
- /// <exception cref="ArgumentOutOfRangeException">Payload size is out of range. For maximum allowed message size see <see cref="PushNotificationMessage.MaxPayloadSize"/></exception>
- /// <exception cref="MessageSendException">Failed to send message for any reason.</exception>
- /// <returns>The result instance with relevant information for this send operation.</returns>
- public MessageSendResult Send(Uri uri)
- {
- Guard.ArgumentNotNull(uri, "uri");
- // Create payload or reuse cached one.
- var payload = GetOrCreatePayload();
- // Create and initialize the request object.
- var request = CreateWebRequest(uri, payload);
- var result = SendSynchronously(payload, uri, request);
- return result;
- }
- /// <summary>
- /// Asynchronously send this messasge to the destination address.
- /// </summary>
- /// <remarks>
- /// This method uses the .NET Thread Pool. Use this method to send one or few
- /// messages asynchronously. If you have many messages to send, please consider
- /// of using the synchronous method with custom (external) queue-thread solution.
- ///
- /// Note that properties of this instance may be changed by different threads while
- /// sending, but once the payload created, it won't be changed until the next send.
- /// </remarks>
- /// <param name="uri">Destination address uri.</param>
- /// <param name="messageSent">Message sent callback.</param>
- /// <param name="messageError">Message send error callback.</param>
- /// <exception cref="ArgumentNullException">One of the arguments is null.</exception>
- /// <exception cref="ArgumentOutOfRangeException">Payload size is out of range. For maximum allowed message size see <see cref="PushNotificationMessage.MaxPayloadSize"/></exception>
- public void SendAsync(Uri uri, Action<MessageSendResult> messageSent = null, Action<MessageSendResult> messageError = null)
- {
- Guard.ArgumentNotNull(uri, "uri");
- // Create payload or reuse cached one.
- var payload = GetOrCreatePayload();
- // Create and initialize the request object.
- var request = CreateWebRequest(uri, payload);
- SendAsynchronously(
- payload,
- uri,
- request,
- messageSent ?? (result => { }),
- messageError ?? (result => { }));
- }
- #endregion
- #region Protected & Virtuals
- /// <summary>
- /// Override to create the message payload.
- /// </summary>
- /// <returns>The messasge payload bytes.</returns>
- protected virtual byte[] OnCreatePayload()
- {
- return _payload;
- }
- /// <summary>
- /// Override to initialize the message web request with custom headers.
- /// </summary>
- /// <param name="request">The message web request.</param>
- protected virtual void OnInitializeRequest(HttpWebRequest request)
- {
- }
- /// <summary>
- /// Check the size of the payload and reject it if too big.
- /// </summary>
- /// <param name="payload">Payload raw bytes.</param>
- protected abstract void VerifyPayloadSize(byte[] payload);
- /// <summary>
- /// Safely set oldValue with newValue in case that are different, and raise the dirty flag.
- /// </summary>
- /// <typeparam name="T">The type of the value.</typeparam>
- /// <param name="oldValue">The old value.</param>
- /// <param name="newValue">The new value.</param>
- protected void SafeSet<T>(ref T oldValue, T newValue)
- {
- lock (_sync)
- {
- if (!object.Equals(oldValue, newValue))
- {
- oldValue = newValue;
- IsDirty = true;
- }
- }
- }
- #endregion
- #region Privates
- /// <summary>
- /// Synchronously send this message to the destination uri.
- /// </summary>
- /// <param name="payload">The message payload bytes.</param>
- /// <param name="uri">The message destination uri.</param>
- /// <param name="payload">Initialized Web request instance.</param>
- /// <returns>The result instance with relevant information for this send operation.</returns>
- private MessageSendResult SendSynchronously(byte[] payload, Uri uri, HttpWebRequest request)
- {
- try
- {
- // Get the request stream.
- using (var requestStream = request.GetRequestStream())
- {
- // Start to write the payload to the stream.
- requestStream.Write(payload, 0, payload.Length);
- // Switch to receiving the response from MPNS.
- using (var response = (HttpWebResponse)request.GetResponse())
- {
- var result = new MessageSendResult(this, uri, response);
- if (response.StatusCode != HttpStatusCode.OK)
- {
- throw new InvalidOperationException(string.Format(Resources.ServerErrorStatusCode, response.StatusCode));
- }
- return result;
- }
- }
- }
- catch (WebException ex)
- {
- var result = new MessageSendResult(this, uri, ex);
- throw new MessageSendException(result, ex);
- }
- catch (Exception ex)
- {
- var result = new MessageSendResult(this, uri, ex);
- throw new MessageSendException(result, ex);
- }
- }
- /// <summary>
- /// Asynchronously send this message to the destination uri using the HttpWebRequest context.
- /// </summary>
- /// <param name="payload">The message payload bytes.</param>
- /// <param name="uri">The message destination uri.</param>
- /// <param name="payload">Initialized Web request instance.</param>
- /// <param name="sent">Message sent callback.</param>
- /// <param name="error">Message send error callback.</param>
- /// <returns>The result instance with relevant information for this send operation.</returns>
- private void SendAsynchronously(byte[] payload, Uri uri, HttpWebRequest request, Action<MessageSendResult> sent, Action<MessageSendResult> error)
- {
- try
- {
- // Get the request stream asynchronously.
- request.BeginGetRequestStream(requestAsyncResult =>
- {
- try
- {
- using (var requestStream = request.EndGetRequestStream(requestAsyncResult))
- {
- // Start writing the payload to the stream.
- requestStream.Write(payload, 0, payload.Length);
- }
- // Switch to receiving the response from MPNS asynchronously.
- request.BeginGetResponse(responseAsyncResult =>
- {
- try
- {
- using (var response = (HttpWebResponse)request.EndGetResponse(responseAsyncResult))
- {
- var result = new MessageSendResult(this, uri, response);
- if (response.StatusCode == HttpStatusCode.OK)
- {
- sent(result);
- }
- else
- {
- error(result);
- }
- }
- }
- catch (Exception ex3)
- {
- error(new MessageSendResult(this, uri, ex3));
- }
- }, null);
- }
- catch (Exception ex2)
- {
- error(new MessageSendResult(this, uri, ex2));
- }
- }, null);
- }
- catch (Exception ex1)
- {
- error(new MessageSendResult(this, uri, ex1));
- }
- }
- /// <summary>
- /// Create a payload and verify its size.
- /// </summary>
- /// <returns>Payload raw bytes.</returns>
- private byte[] GetOrCreatePayload()
- {
- if (IsDirty)
- {
- lock (_sync)
- {
- if (IsDirty)
- {
- var payload = OnCreatePayload() ?? new byte[0];
- DebugOutput(payload);
- VerifyPayloadSize(payload);
- _payload = payload;
- IsDirty = false;
- }
- }
- }
- return _payload;
- }
- private HttpWebRequest CreateWebRequest(Uri uri, byte[] payload)
- {
- var request = (HttpWebRequest)WebRequest.Create(uri);
- request.Method = WebRequestMethods.Http.Post;
- request.ContentType = "text/xml; charset=utf-8";
- request.ContentLength = payload.Length;
- request.Headers[Headers.MessageId] = Id.ToString();
- // Batching interval is composed of the message priority and the message class id.
- int batchingInterval = ((int)SendPriority * 10) + NotificationClassId;
- request.Headers[Headers.BatchingInterval] = batchingInterval.ToString();
- OnInitializeRequest(request);
- return request;
- }
- #endregion
- #region Diagnostics
- [Conditional("DEBUG")]
- private static void DebugOutput(byte[] payload)
- {
- string payloadString = Encoding.ASCII.GetString(payload);
- Debug.WriteLine(payloadString);
- }
- #endregion
- }
- }
|