Adding push notification tags from an Azure Mobile Apps client

When a mobile app registers for push notifications using an Azure App Service Mobile Apps backend, there are two default tags that can get added to the registration in Azure Notification Hubs: the installation ID, which is unique to the app on a given device, and the user ID, which is only added when the user has been previously authenticated. Any other tags that get supplied by the client are ignored, which is by design. (Note that this differs from Mobile Services, where the client could supply any tag and there were hooks into the registration process on the backend to validate tags on incoming registrations.)

Because the client can’t add tags and at the same time there is not service-side hooks into the push notification registration process, the client needs to do the work of adding new tags to a given registration. To let the client supply tags, we need to add an API to our mobile app backend that the client to call to add tags.

Before we dive into creating a new custom API, let’s briefly discuss the new installation ID concept, which is key to the whole thing. Also note that this post uses a .NET backend, but the same principles apply for a node.js backend.

Introducing InstallationId

The Mobile Apps client library now defines an installation ID, which didn’t exist in Mobile Services. The installation ID is a GUID that is specific to an installed app on a given mobile device, to the point that when you uninstall and reinstall the app on the same device you get a new GUID. The installation ID can be obtained from the Mobile client API, as follows:

 MobileServiceClient.InstallationId;

This same API exists in the Mobile Apps library on all client device platforms. How this works is that when the client registers for push notifications with Notification Hubs (through the Mobile Apps client), this installation ID is added automatically as a tag. This tag is then used as an index into a specific registration, and the Notification Hubs client has new APIs that let you work with registrations based on this value.  Here is what an installation ID tag value looks like:

 $InstallationId:{9c105281-c188-44b8-b1b5-8d08725c4785}

When working with the Notification Hubs client, all you will need is the GUID value, which you get from the MobileServiceClient.

The UpdateTags controller

To enable the client to add tags to its registration, the first thing I did was create a new ApiController named UpdateTagsController in my Mobile App backend, and in the initializer I added the standard code that creates the NotificationHubClient instance based on the hub used by my Mobile App backend:

 private NotificationHubClient hubClient;

protected override void Initialize(HttpControllerContext controllerContext)
{
    base.Initialize(controllerContext);

    // Get the Mobile App settings.
    MobileAppSettingsDictionary settings =
        this.Configuration.GetMobileAppSettingsProvider().GetMobileAppSettings();

    // Get the Notification Hubs credentials for the Mobile App.
    string notificationHubName = settings.NotificationHubName;
    string notificationHubConnection = settings
        .Connections[MobileAppSettingsKeys.NotificationHubConnectionString]
        .ConnectionString;

    // Create the notification hub client.
    hubClient = NotificationHubClient
        .CreateClientFromConnectionString(notificationHubConnection, 
            notificationHubName);
}

Then I implemented a GET by ID method so that I could retrieve tags for a given installation ID:

 // GET api/UpdateTags/Id
[HttpGet]
public async Task<List<string>> GetTagsByInstallationId(string Id)
{
    try
    {
        // Return the installation for the specific ID.
        var installation =  await hubClient.GetInstallationAsync(Id);
        return  installation.Tags as List<string>;
    }
    catch(MessagingException ex)
    {
        throw ex;
    }
}

In my scenario, the client didn’t really need to use this, but it would be useful to inspect existing tags, and I did use it when checking tags using a REST client, like fiddler. Here’s the HTTP GET request to the new API:

 GET https://todolist.azurewebsites.net/api/updatetags/9c105281-c188-44b8-b1b5-8d08725c4785 HTTP/1.1
User-Agent: Fiddler
Host: todolist.azurewebsites.net
ZUMO-API-VERSION: 2.0.0

Then you get back the existing tags:

 HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 82
Content-Type: application/json; charset=utf-8
Expires: 0
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Wed, 20 Jan 2016 21:13:15 GMT

["_UserId:sid:4fce9655d0610c53a1f236ae7befc14x"]

Note that the only tag returned is the user ID, because this registration we done with a logged-in user. The installation ID isn’t returned by the API, but you can see it there by using the Server Explorer in Visual Studio:

image

If your app needs to send notifications to a specific device, you probably want to store the installation ID from your user along with the user ID or some other tracking data in your backend.

Adding new tags: the POST method

The method mapped to the POST request expects a JSON array of tags from the client, which get added to the registration for a given installation ID. (If the input is not a valid JSON array, an exception returns an HTTP 500.) A single PartialUpdateOperation in a list contains the JArray of tags to be added (you can only have one tags operation in a given request to PatchInstallationAsync().

 // POST api/UpdateTags/Id
[HttpPost]
public async Task<HttpResponseMessage> AddTagsToInstallation(string Id)
{
    // Get the tags to update from the body of the request.
    var message = await this.Request.Content.ReadAsStringAsync();

    // Validate the submitted tags.
    if (string.IsNullOrEmpty(message) || message.Contains("sid:"))
    {
        // We can't trust users to submit their own user IDs.
        return new HttpResponseMessage(HttpStatusCode.BadRequest);
    }

    // Verify that the tags are a valid JSON array.
    var tags = JArray.Parse(message);

    // Define a collection of PartialUpdateOperations. Note that 
    // only one '/tags' path is permitted in a given collection.
    var updates = new List<PartialUpdateOperation>();

    // Add a update operation for the tag.
    updates.Add(new PartialUpdateOperation
    {
        Operation = UpdateOperationType.Add,
        Path = "/tags",
        Value = tags.ToString()
    });

    try
    {
        // Add the requested tag to the installation.
        await hubClient.PatchInstallationAsync(Id, updates);

        // Return success status.
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
    catch(MessagingException)
    {
        // When an error occurs, return a failure status.
        return new HttpResponseMessage(HttpStatusCode.InternalServerError);
    }
}
Important: there is currently a bug where attempting to add a tag that starts with whitespace causes all tags to be removed from the registration, including the installation ID. This is pretty bad, so until we get it fixed you may want to trim tags that are supplied by end users.

Here is an example of calling the POST method directly using a REST client, like Fiddler:

 POST http://todolist.azurewebsites.net/api/updatetags/9c105281-c188-44b8-b1b5-8d08725c4785 HTTP/1.1
User-Agent: Fiddler
Host: localhost:51795
ZUMO-API-VERSION: 2.0.0
Content-Length: 20

['broadcast','test']

Next we will see how to call this new API from a .NET client app.

Calling the new API from the client

With the new API tested and published, it’s very easy to call it from the client app after registration is complete. The following code example calls InvokeApiAsync() on the MobileServiceClient after registration succeeds to send a two tag update request to the /api/updatetags/<installation_id> endpoint:

 // Register for template push notifications.
await App.MobileService.GetPush()
.RegisterAsync(channel.Uri, templates);

// Define two new tags as a JSON array.
var body = new JArray();
body.Add("broadcast");
body.Add("test");

// Call the custom API '/api/updatetags/<installationid>' 
// with the JArray of tags.
var response = await App.MobileService
    .InvokeApiAsync("updatetags/" 
    + App.MobileService.InstallationId, body);

Now, you can send notifications only to specific devices depending on the registered tags.

Final thoughts

Well, that’s all there is to adding tags from to a registration using the installation ID from the client. Pretty easy stuff. Although you do need a valid installation ID to successfully call these APIs, you might want to also require authentication, just to keep any malicious users from trying to add tags using some randomly guessed installation ID.

Cheers!

Glenn Gailey