InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Push-Notifications – Surviving application restarts

This is the fourth article about the .NET component I published at CodePlex recently. To all related articles, click here.

If you subscribe to a folder on an Exchange mailbox or public folder, the server will try to send notifications to the registered endpoint of yours upon every modification. If your application is shutdown, Exchange will try to reach it for a certain amount of time. Once this time span has elapsed, it will delete the subscription. If the application is restarted and a new subscription is created, it will be notified about all subsequent events. But for certain use cases, it is imperative that no events are missed – even events which occurred between application downtime must be catched. One example for this is a synchronization applications which keeps a sql server database (like a CRM application) and Exchange folders in sync.

For these types of scenarios, all Exchange events carry a watermark with them. This watermark is opaque to the client, but for the Exchange server it contains enough information to reconstruct changes made since the watermark was received. To use this features, an application has to save each watermark it gets. The watermark can be sent to the Exchange server along with the subscription request for a folder. Exchange will then replay the events which happened since that watermark has been generated.

I’ve implemented this feature in my Push Notification component in the SubscriptionCollection class. It can be serialized to either a System.IO.Stream or System.Xml.XmlWriter. The latter one uses the System.Runtime.Serialization.DataContractSerializer to perform the serialization. And the way it is used, it requires .NET Framework 3.5 Service Pack 1. If you cannot rely on having this version installed on your clients machines, you should probably use the binary serialization instead.

Here is a small code sample on how to use the serialization feature (this is an example only, so the exception handling is far from optimal…)

   1: using System;
   2: using System.ComponentModel;
   3: using System.IO;
   4: using System.Net;
   5: using InfiniTec.Exchange.Notifications;
   6: using InfiniTec.Threading;
   7:  
   8: namespace NewMailNotificationExample
   9: {
  10:     internal class Program
  11:     {
  12:         private const string _StateSaverFilename = "notificationstate.bin";
  13:  
  14:         private static void Main()
  15:         {
  16:             // Ignore any certificate errors
  17:             ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
  18:             
  19:             // Setup the adapter which will be used to call into the Exchange WebService  
  20:             var adapter = new ExchangeWebServicesAdapter(new Uri("https://w2k3x64/ews/exchange.asmx"),
  21:                                                          new NetworkCredential("administrator", "password", "contoso"));
  22:  
  23:  
  24:             // Setup a listener that listens on port 80 on the local computer          
  25:  
  26:             using (var listener = new PushNotificationListener())
  27:             {
  28:                 Console.Out.WriteLine("Starting Notification Service...");
  29:                 listener.Start();
  30:  
  31:                 SubscriptionCollection subscriptionCollection;
  32:  
  33:                 if (File.Exists(_StateSaverFilename))
  34:                 {
  35:                     using (var stream = File.OpenRead(_StateSaverFilename))
  36:                     {
  37:                         subscriptionCollection = SubscriptionCollection.Deserialize(stream);
  38:                         subscriptionCollection.SubscriptionRestartCompleted += 
  39:                             SubscriptionCollection_OnSubscriptionRestartCompleted;
  40:                         subscriptionCollection.SubscriptionRestartProgressChanged +=
  41:                             SubscriptionCollection_OnSubscriptionRestartProgressChanged;
  42:  
  43:                         subscriptionCollection.RestartAsync(listener, adapter);
  44:                     }
  45:                 }
  46:                 else
  47:                 {
  48:                     // Create a new subscription collection to manage all the subscriptions  
  49:                     // Register for a NewMail notification on the inbox of the administrator
  50:                     subscriptionCollection = new SubscriptionCollection(adapter)
  51:                                                  {
  52:                                                      {new[] {new FolderReference(WellKnownFolderId.Inbox)}, EventTypes.NewMail}
  53:                                                  };
  54:                 }
  55:  
  56:                 Console.Out.WriteLine("Creating subscription");
  57:                 foreach (var subscription in subscriptionCollection)
  58:                 {
  59:                     // Write a line to the console for each new mail received  38:  
  60:                     subscription.NewMail += (sender, e) =>
  61:                                             Console.Out.WriteLine(string.Format("{0}: New Mail arrived in your inbox", e.Timestamp));
  62:                     subscription.Start(listener);
  63:                 }
  64:                 Console.Out.WriteLine("Waiting for notifications... Hit [Enter] to quit...");
  65:                 Console.ReadLine();
  66:  
  67:                 Console.Out.WriteLine("Saving the current state of the notification listener...");
  68:                 using (var stream = File.OpenWrite(_StateSaverFilename))
  69:                 {
  70:                     subscriptionCollection.Serialize(stream);
  71:                 }
  72:                 Console.Out.WriteLine("State saved to {0}", Path.GetFullPath(_StateSaverFilename));
  73:             }
  74:         }
  75:  
  76:         private static void SubscriptionCollection_OnSubscriptionRestartProgressChanged(object sender, ProgressChangedEventArgs args)
  77:         {
  78:             Console.Out.WriteLine("Subscription restart {0}% complete.", args.ProgressPercentage);
  79:         }
  80:  
  81:         private static void SubscriptionCollection_OnSubscriptionRestartCompleted(object sender, AsyncCompletedEventArgs<SubscriptionRestartErrorSummary> args)
  82:         {
  83:             Console.Out.WriteLine("Subscription restart is complete. {0} subscriptions could not be restarted.", args.Result.Errors.Count);
  84:         }
  85:     }
  86: }

 

This essentially is the sample from my first blog post in this series, but enhanced to take advantages of the restart capabilities of the SubscriptionCollection class. The program checks to see if there is a saved state from a previous run available. If found, it is deserialized and restarted (lines 35 to 43). Otherwise a new instance is created. Finally, the events are wired to the individual subscriptions (line 57 to 62). And once the application stops, the current state is serialized to a file.

Since the subscriptions are restarted asynchronously in the background, the SubscriptionRestartCompleted and SubscriptionRestartProgressChanged events are used to keep track of the deserialization process. The progress changed event handler is raised for each subscription restarted. Once complete, the SubscriptionRestartCompleted handler is raise. The event args this event handler contain information about all failed restart attempts.


Posted by Henning Krause on Monday, January 12, 2009 11:36 PM, last modified on Monday, January 12, 2009 11:36 PM
Permalink | Post RSSRSS comment feed

Comments (1) -

On 2/3/2009 7:09:37 PM John Billingsly United States wrote:

John Billingsly

I believe that watermarks are sent for every response back from the server. How can the watermak be updated and saved so that when rescribe only get those changes after that last succesful item returned.