Registering from your App Backend

As mentioned in the previous sections, devices must create one or more registrations in a notification hub in order to receive push notifications. One way to complete this registration is to have the mobile devices contact the notification hub directly to specify (or update) their PNS handle and their tags. This approach has a number of limitations and there are some scenarios in which it’s advisable to contact your own app backend when a device refreshes its registration. The backend then calls the notification hub.

When to register from your app backend

There are two scenarios in which it’s advisable to route device registrations through your app backend.

Tags must be secured

When a device registers directly with a notification hub, it can specify any tag it wants. This is not a problems if tags are public interest groups that any device can subscribe to (for example, news feeds regarding sport teams). However, this can be a problem when some tags are available only for some users.

In order to register each user only to the allowed tags, you must route the registration operations through your own app backend, which can perform user authentication and authorize the registration for the required tags.

The most common example of this scenario is using tags to represent user IDs. In this case, you want to prevent devices from registering to tags that represent other users, as they would receive that other user’s notifications.

Tags are modified by your app backend

Registering from the device is convenient and enables you to quickly set up push notifications and rich routing to interest groups. However, registering from the device does not work very well if you want to change tags as a result of events that occur on other devices.

Consider two scenarios: if the tags on Alice’s phone are set as a result of events occurring on Alice’s phone, then it is easy for the app to update the tags in the notification hub. If, on the other hand, tags must change as a result of events happening on other devices (for example, Alice’s laptop when logged on to a website), then the device will have to wait for the app to be active again in order to reflect the changes in the notification hub.

A specific example of the previous scenario is a music app that includes a web experience, and a mobile app. In this case, a specific user might follow a new band through the web site, and want the device to start receiving notifications about the new band as soon as possible. Another example is when tags come from other parts of your backend (a CRM, for example), which can change the status of the user from Silver to Gold. This change can result in a new tag being set on all users’ registrations.

How to register from the backend

When registering a device, a notification hub must distinguish between different devices. This cannot be accomplished just by looking at the PNS handles, as they are transient and not unique. To solve this problem, Notification Hubs generates long-living registration IDs that each device must store locally in order to be able to refer to its own registration each time it updates its PNS handle, tags or template.

The following figure shows the registration flow for native notifications:

  1. On the device, if no registration id is stored locally,

    1. Call the app backend to get registration id.

    2. App backend calls notification hubs to create a new registration ID, and then return the ID back to the device.

    3. Store the registration ID in device local storage.

  2. On the device, retrieve the registration ID from local storage:

    1. Call the app backend, providing the registration ID, PNS handle, and tags.

    2. App backend creates or updates the corresponding registration on the notification hub.

    3. If the app backend returns status code 410, then a new registration id must be created. Delete the registration ID from local storage and restart from step 1.

Backend Registration

The flow for template notifications is analogous. The only differences are as follows:

  1. If a device uses multiple templates, then it must store one registration ID per template.

  2. You can identify the templates by using the TemplateName property of the registration.

The following code is an example of back-end endpoints.

public class RegisterController : ApiController
    {

        private NotificationHubClient hub;

        public RegisterController()
        {
            hub = NotificationHubClient.CreateClientFromConnectionString("Endpoint=sb://buildhub-ns.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=DuWV4SQ08poV6HZly8O/KQNWv3YRTZlExJxu3pNCjGU=", "build2014_2");
        }
        
        public class DeviceRegistration
        {
            public string Platform { get; set; }
            public string Handle { get; set; }
            public string[] Tags { get; set; }
        }

        // POST api/register
        // This creates a registration id
        public async Task<string> Post()
        {
            return await hub.CreateRegistrationIdAsync();
        }

        // PUT api/register/5
        // This creates or updates a registration (with provided PNS handle) at the specified id
        public async void Put(string id, DeviceRegistration deviceUpdate)
        {
            // IMPORTANT: add logic to make sure that caller is allowed to register for the provided tags
            
            RegistrationDescription registration = null;
            switch (deviceUpdate.Platform)
            {
                case "mpns":
                    registration = new MpnsRegistrationDescription(deviceUpdate.Handle);
                    break;
                case "wns":
                    registration = new WindowsRegistrationDescription(deviceUpdate.Handle);
                    break;
                case "apns":
                    registration = new AppleRegistrationDescription(deviceUpdate.Handle);
                    break;
                case "gcm":
                    registration = new GcmRegistrationDescription(deviceUpdate.Handle);
                    break;
                default:
                    throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            registration.RegistrationId = id;
            registration.Tags = new HashSet<string>(deviceUpdate.Tags);

            try
            {
                await hub.CreateOrUpdateRegistrationAsync(registration);
            } catch (MessagingException e) {
                ReturnGoneIfHubResponseIsGone(e);
            }
        }

        // DELETE api/register/5
        public async void Delete(string id)
        {
            await hub.DeleteRegistrationAsync(id);
        }


        private static void ReturnGoneIfHubResponseIsGone(MessagingException e)
        {
            var webex = e.InnerException as WebException;
            if (webex.Status == WebExceptionStatus.ProtocolError)
            {
                var response = (HttpWebResponse)webex.Response;
                if (response.StatusCode == HttpStatusCode.Gone)
                    throw new HttpRequestException(HttpStatusCode.Gone.ToString());
            }
        }
    }

Note that in the preceding code, you must add the logic to ensure that the client calling that endpoint is authorized to register for the requested tags. Also, your backend can add the tags itself (for example, a userid tag).

The following code example shows how to implement the registration method for a Windows Store app, from the device, with the preceding endpoints:

class RegisterClient
    {
        private string POST_URL = "{your back-end endpoints}";

        private class DeviceRegistration
        {
            public string Platform { get; set; }
            public string Handle { get; set; }
            public string[] Tags { get; set; }
        }

        public async Task RegisterAsync(string handle, IEnumerable<string> tags)
        {
            var regId = await RetrieveRegistrationIdOrRequestNewOneAsync();

            var deviceRegistration = new DeviceRegistration
            {
                Platform = "wns",
                Handle = handle,
                Tags = tags.ToArray<string>()
            };

            var statusCode = await UpdateRegistrationAsync(regId, deviceRegistration);

            if (statusCode == HttpStatusCode.Gone)
            {
                // regId is expired, deleting from local storage & recreating
                var settings = ApplicationData.Current.LocalSettings.Values;
                settings.Remove("__NHRegistrationId");
                regId = await RetrieveRegistrationIdOrRequestNewOneAsync();
                statusCode = await UpdateRegistrationAsync(regId, deviceRegistration);
            }

            if (statusCode != HttpStatusCode.Accepted)
            {
                // log or throw
            }
        }

        private async Task<HttpStatusCode> UpdateRegistrationAsync(string regId, DeviceRegistration deviceRegistration)
        {
            using (var httpClient = new HttpClient())
            {
                var putUri = POST_URL + "/" + regId;
                var response = await httpClient.PutAsJsonAsync<DeviceRegistration>(putUri, deviceRegistration);
                return response.StatusCode;
            }
        }

        private async Task<string> RetrieveRegistrationIdOrRequestNewOneAsync()
        {
            var settings = ApplicationData.Current.LocalSettings.Values;
            if (!settings.ContainsKey("__NHRegistrationId"))
            {
                using (var httpClient = new HttpClient())
                {
                    var response = await httpClient.PostAsync(POST_URL, new StringContent(""));
                    if (response.IsSuccessStatusCode)
                    {
                        string regId = await response.Content.ReadAsStringAsync();
                        regId = regId.Substring(1, regId.Length - 2);
                        settings.Add("__NHRegistrationId", regId);
                    }
                    else
                    {
                        throw new Exception();
                    }
                }
            }
            return (string)settings["__NHRegistrationId"];

        }
    }

Storing registration IDs in a back-end database

Sometimes, applications want to keep the registration IDs in the app backend instead of in the device local storage. This usually happens when the app backend already has a way to identify devices (for example, an installationId), and a way to store device information on the backend storage (for example, when migrating from a custom push solution in which PNS handles were stored).

How to modify tags from the backend

If you want to modify tags from the backend, you must have a way for the backend to identify the registrations that are to be modified. This is usually done using a tag.

For example, assume there is a music app in which a user adds a new favorite band from the Web, and the backend adds a tag in the user’s mobile registrations as a result of that. In this case, the app uses a tag to identify the user, and then uses that tag to retrieve the registrations to be updated and update them.

The following code example retrieves the registrations, and adds a new tag to them.

var registrations = await hub.GetRegistrationsByTagAsync("{userId}", 10);
            foreach (var reg in registrations)
            {
                reg.Tags.Add("{newBand}");
                await hub.UpdateRegistrationAsync(reg);
            }

Note that in this example, if you are using templates, we assume that you are adding the tag on all your templates.