-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Migrating 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.
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.
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:
- 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.
- 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.
- 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.
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.