Skip to content

Migrating from PushSharp 2.x to 3.x

VitorNeilAvelino edited this page Oct 28, 2016 · 6 revisions

How to Migrate from PushSharp 2.x to 3.x

I've had a number of users asking about how to migrate from 2.x to 3.x. While this isn't meant to be a guide on exactly how to do that (because it will depend on each developer), but it will hopefully help highlight the differences.

Why the concept of a single broker no longer exists:

There was much more confusion than I would have expected around how PushSharp previously supported registering multiple services using a single broker, that I decided to let you the developer decide how to work with multiple brokers.

This is really simple. For most people, they will want to create an instance of a platform specific broker for each platform they want to use. In some cases where you are supporting multiple apps, you may need to create multiple brokers for each platform as well (one for each app).

It might make sense for you to define your own objects to keep track of broker instances. Typically this would involve some sort of dictionary, and could be as simple as creating a Dictionary<string, ApnsServiceBroker> type of dictionary for each platform, where the key is your application's id.

Or, here's something I would likely use:

public class AppPushBrokers
{
	public ApnsServiceBroker Apns { get;set; }
	public GcmServiceBroker Gcm { get;set; }
}

// Somewhere, setup our dictionary of apps we will send to
var apps = new Dictionary<string, AppPushBrokers> {
	{ 
		"com.altusapps.someapp", 
		new AppPushBrokers {
			Apns = new ApnsServiceBroker (someAppApnsConfig),
			Gcm = new GcmServiceBroker (someAppGcmConfig)
		}
	},
	{
		"com.altusapps.anotherapp",
		new AppPushBrokers {
			Apns = new ApnsServiceBroker (anotherAppApnsConfig),
			Gcm = new GcmServiceBroker (anotherAppGcmConfig)
		}
	}
};


// Now pick the right set of brokers to send notifications to
apps["com.altusapps.someapp"].Apns.Send (new ApnsNotification (..));

This way you are left with as much flexibility as you need. Only need a single platform for a single app? No problem, just create one instance. Need a more complex solution? You're free to set it up however you like.

Ultimately I think this will cause much less developer confusion, at the small expense of having to create a simple dictionary or object model to achieve what you want.

Where are all the Fluent notification payload helpers?

Apns expects a JSON payload, GCM a key/value pair in JSON format, and Windows expects XML. Previously, PushSharp attempted to provide helper methods to construct these payloads without having to know the raw data behind them. This no longer exists in 3.x and you must provide a raw payload in the format the platform is expecting.

This will probably be the most difficult adjustment while migrating.

There are a couple of reasons behind this decision:

  1. It became very onerous to maintain these methods as the platform changed, and it was easy to quickly fall behind which didn't make users happy.
  2. Users weren't forced to know enough about the platform's expectations and this lead to a lot of unnecessary support questions simply because of not reading the platform's documentation properly, or being confused about how PushSharp was generating a payload.
  3. The 3.x release is all about simplicity. In practice, it doesn't seem to be a lot of work to write the code to generate the appropriate payload string without the helpers. This eliminates all confusion about the payload of notifications.

Where did all the events go?

In 2.x there were a number of events you could subscribe to:

  • OnChannelException
  • OnServiceException
  • OnNotificationFailed
  • OnDeviceSubscriptionExpired
  • OnDeviceSubscriptionChanged
  • OnChannelCreated
  • OnChannelDestroyed

There was a lot of ambiguity in 2.x around which events should be called when. For example, if a device's subscription was changed, should the OnNotificationFailed be called in addition to OnDeviceSubscriptionChanged? Should OnDeviceSubscriptionExpired also be called too?

In 3.x to make things less ambiguous I've decided to only use 2 events:

  • OnNotificationFailed
  • OnNotificationSucceeded

This way, if anything causes the notification to not be successfully sent, for any reason at all, there is one single point to find this out.

First of all, the OnNotificationFailed event has an AggregateException parameter. You will need to inspect the actual inspections inside the aggregate. A convenient way to do this is to call aggregateException.Handle (ex => { /* logic to handle exception */ });.

In general, the aggregate exception parameter will contain NotificationException instances. Each platform has its own subclassed type of NotificationException (eg: ApnsNotificationException, GcmNotificationException, etc), so you might want to check if you can cast the NotificationException to the appropriate platform's implementation to get access to more detailed information (such as the platform specific notification type).

Along with these exceptions, there are DeviceSubscriptionExpiredException which indicates if a device token/registration id/channel uri/etc is expired. This exception type contains an OldRegistrationId property indicating which registration id is expired, and a NewRegistrationId property. The presence of a non-null NewRegistrationId property indicates the subscription has changed.

There is also the possibility of a RetryAfterException on some platforms. If you encounter this exception, it means you are being rate limited and should not try sending more notifications until after the date indicated in the RetryAfterUtc property.

Finally, if you send a multicast notification (sending to more than one registration id) on GCM (Google Cloud Messaging), it is possible to encounter a GcmMulticastResultException. This exception contains a dictionary of failed notifications (and their related exceptions), and a list of successful notifications.

Here is a very generic implementation of how you could handle the OnNotificationFailed event:

// Wire up events
broker.OnNotificationFailed += (notification, aggregateEx) => {

	aggregateEx.Handle (ex => {
	
		// See what kind of exception it was to further diagnose
		if (ex is NotificationException) {
			// Deal with the failed notification
			var notification = ex.Notification;
			Console.WriteLine ($"Notification Failed: {notification}");
		} else if (ex is DeviceSubscriptionExpiredException) {

			var oldId = ex.OldSubscriptionId;
			var newId = ex.NewSubscriptionId;

			Console.WriteLine ($"Device RegistrationId Expired: {oldId}");

			if (!string.IsNullOrEmpty (newId)) {
				// If this value isn't null, our subscription changed and we should update our database
				Console.WriteLine ($"Device RegistrationId Changed To: {newId}");
			}
		} else if (ex is RetryAfterException) {
			// If you get rate limited, you should stop sending messages until after the RetryAfterUtc date
			Console.WriteLine ($"Rate Limited, don't send more until after {ex.RetryAfterUtc}");
		} else {
			Console.WriteLine ("Notification Failed for some (Unknown Reason)");
		}

		// Mark it as handled
		return true;
	});
};

You can handle things a bit more platform specific as well, like in this case for GCM:

var config = new GcmConfiguration ("GCM-SENDER-ID", "AUTH-TOKEN", null);

// Create a new broker
var broker = new GcmServiceBroker (config);
    
// Wire up events
broker.OnNotificationFailed += (notification, aggregateEx) => {

	aggregateEx.Handle (ex => {
	
		// See what kind of exception it was to further diagnose
		if(ex is GcmNotificationException) {
			var x = ex as GcmNotificationException;

			// Deal with the failed notification
			GcmNotification n = x.Notification;
			string description = x.Description;

			Console.WriteLine($"Notification Failed: ID={n.MessageId}, Desc={description}");
		}
		else if(ex is GcmMulticastResultException) {

			var x = ex as GcmMulticastResultException;

			foreach(var succeededNotification in x.Succeeded) {
				Console.WriteLine($"Notification Failed: ID={succeededNotification.MessageId}");
			}

			foreach(var failedKvp in x.Failed) {
				GcmNotification n = failedKvp.Key;
				var e = failedKvp.Value as GcmNotificationException;

				Console.WriteLine($"Notification Failed: ID={n.MessageId}, Desc={e.Description}");
			}

		}
		else if(ex is DeviceSubscriptionExpiredException) {
			var x = (DeviceSubscriptionExpiredException)ex;

			string oldId = x.OldSubscriptionId;
			string newId = x.NewSubscriptionId;

			Console.WriteLine($"Device RegistrationId Expired: {oldId}");

			if(!string.IsNullOrEmpty(newId)) {
				// If this value isn't null, our subscription changed and we should update our database
				Console.WriteLine($"Device RegistrationId Changed To: {newId}");
			}
		}
		else if(ex is RetryAfterException) {
			var x = ex as RetryAfterException;
			// If you get rate limited, you should stop sending messages until after the RetryAfterUtc date
			Console.WriteLine($"Rate Limited, don't send more until after {x.RetryAfterUtc}");
		}
		else {
			Console.WriteLine("Notification Failed for some (Unknown Reason)");
		}

		// Mark it as handled
		return true;
	});
};

broker.OnNotificationSucceeded += (notification) => {
	Console.WriteLine ("Notification Sent!");
};

// Start the broker
broker.Start ();

foreach (var regId in MY_REGISTRATION_IDS) {
	// Queue a notification to send
	broker.QueueNotification (new GcmNotification {
		RegistrationIds = new List<string> { 
			regId
		},
		Data = JObject.Parse ("{ \"somekey\" : \"somevalue\" }")
	});
}
   
// Stop the broker, wait for it to finish   
// This isn't done after every message, but after you're
// done with the broker
broker.Stop ();

As for the other missing exception events (OnChannelException and OnServiceException), the exceptions that caused these events to fire in 2.x will be surfaced inside the OnNotificationFailed event instead in 3.x+.

Finally, OnChannelCreated and OnChannelDestroyed are currently not implemented in 3.x+ since there is no Auto-Scaling and the developer should know when this happens as they will explicitly be changing the broker's scale. If auto-scaling makes it back into 3.x+, these events (or similar) will likely be added again.