Building a Notification Hub for Windows Phone on top of Windows Azure Mobile Services

For most applications, notifications are not exactly critical. Granted, a scientific calculator will not benefit from having an in-app notification hub. On the other hand, there are cases when you want to let the user know about what’s new and what changes before an update or including information in the changelog. That’s where a custom notification hub control can come in really handy.

What goes into a notification?

Before jumping to actual coding, let’s think about what actually should go in a notification. We obviously want to display the information in the form of the simplest message possible. A title and a description should do it. But a notification also usually implies that there is some action tied to it, be it opening another application or a third-party location. So maybe including a URI would be another valid addition. All in all, we end up with this layout:

image

Fantastic. You can see that there are two extra properties that I haven’t mentioned – Id and TimeStamp. Since Azure Mobile Services are used, one of the core ways to determine the identity of an entity is by its unique integer identifier in the data table – Id takes care of holding the proper value. The TimeStamp property might not carry a unique value, but will tell the user when the notification was created. Ultimately, in the application itself it is possible to set filters to only show notifications from a given date range, but that’s a different topic.

In C# code, the class above will look like this:

using Microsoft.WindowsAzure.MobileServices;
using System;

namespace Hilltop.CoreTools.Models
{
  [DataTable("notifications")]
  public class Notification
  {
    public int? Id { get; set; }
    public DateTime TimeStamp { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public string Url { get; set; }
  }
}

Notice the reference to Microsoft.WindowsAzure.MobileServices – make sure you install the Azure Mobile Services Windows Phone SDK in order to be able to use it. I personally prefer using NuGet for this, so feel free to use this command to get the package:

PM> Install-Package WindowsAzure.MobileServices

The Core Control

Now that there is a defined notification model, I can start working on the core control itself. I’ll start by defining the skeleton via a INotificationCenter interface:

 using System.Threading.Tasks;

namespace Hilltop.CoreTools.Interfaces
{
  interface INotificationCenter
  {
    void Initialize();
    void GetCurrent();
    void Clear();

    Task CreateNotification(string title, string content, string url);
  }
}

GetCurrent will handle the acquisition of the current notification stack, Clear will erase all existing notifications and CreateNotifications will help the developer create notifications directly from the app.

SECURITY NOTE: It is important to remember, however, that notification creation should probably be secured – the way the notification hub control works, it provides a unified “connector” to the service and it is up to the developer to have the proper separation of roles and capabilities available to different app user categories.

Without going into the boring details of dependency property registration, here is what the ultimate class for the NotificationCenter control will look like:

image

Notice that I have MobileService declared in the Fields section. This is nothing else but the core managed Azure Mobile Services client. By default, it is null:

 public static MobileServiceClient MobileService = null;

We have an entire set of properties that determine the appearance of the notification entity in the global notification list. It is possible to set the icon associated with a given notification (usually taken from the context of the application itself but can be remote as well) as well as the colors for the content displayed.

Most important, however, is to have these two properties: ZumoKey and ZumoUrl. These will be used by the managed client to connect to the specific Azure Mobile Service instance and retrieve the data.

When the control is loaded in the visual tree, a call to Initialize (this is not the same as InitializeComponent) is made, that will try to connect to the service and retrieve any pending notifications:

 public void Initialize()
{
    if (!string.IsNullOrWhiteSpace(ZumoKey))
    {
        if (!string.IsNullOrWhiteSpace(ZumoUrl))
        {
            MobileService = new MobileServiceClient(ZumoUrl, ZumoKey);

            GetCurrent();
        }
        else
        {
            throw new InvalidOperationException("Missing ZUMO URL.");
        }
    }
    else
    {
        throw new InvalidOperationException("Missing ZUMO key.");
    }
}

When it comes to GetCurrent, it will simply populate the Notifications collection with the data returned by AMS:

 public async void GetCurrent()
{
    if (MobileService != null)
    {
        try
        {
            var data = await MobileService.GetTable().ToListAsync();
            Notifications = new ObservableCollection(data);
        }
        catch
        {
            // Failed to obtain the list of current notifications.
        }
    }
}

Post-initialization, we want to let the user know that the control is ready, so I’ve added the Ready event handler. Here is how it will be invoked once the control has finished the first iteration of the loading routine:

 void NotificationCenter_Loaded(object sender, RoutedEventArgs e)
{
    Initialize();

    if (Ready != null)
    {
        Ready(this, new EventArgs());
        this.Loaded -= NotificationCenter_Loaded;
    }
}

Great. However chances are that at this point you don’t have any notifications available. CreateNotification would be exactly what you need:

 /// 
/// Allows notification insertion directly from the client app.
///
///Notification title.
///Notification content.
/// TRUE if insertion is successful. FALSE if not.
public async Task CreateNotification(string title, string content, string url)
{
    Notification notification = new Notification();
    notification.Content = content;
    notification.Title = title;
    notification.TimeStamp = DateTime.Now;
    notification.Url = url;

    if (MobileService != null)
    {
        try
        {
             await MobileService.GetTable().InsertAsync(notification);
             return true;
        }
        catch
        {
             return false;
        }
     }

     return false;
}

Thanks to the amazing work done by the Windows Azure Mobile Services team, all you really need to do is create a new instance of the Notification model and push it to the aforementioned MobileService client.

As I also mentioned earlier, we’d want the notification to be interactive and actually point to some sort of a resource, that is represented with the help of the Url property. To do this, we handle the notification item selection in the list:

 private void NotificationSelected(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count > 0)
    {
        Notification notification = (Notification)e.AddedItems[0];

        WebBrowserTask task = new WebBrowserTask();
        task.Uri = new Uri(notification.Url);
        task.Show();
    }
}

The WebBrowserTask will use the built in browser (Internet Explorer) to open the associated page. Simple as that.

What do I need to set up in the AMS dashboard?

Not a whole lot. First of all, make sure that you create a new table that has the same identifier as the DataTable attribute in the Notification class. In the example above, I am using notifications as the name, and it is used like that in production with Beem and EnTrance:

image

During development, you might also want to enable Dynamic Schema:

image

SECURITY NOTE: It is important to disable dynamic schema before your app goes in production.

How do I use the control?

Use the standard XAML syntax in one of your pages:

 <ht:NotificationCenter NotificationIcon="/Images/notification.png" ZumoUrl="YOUR_URL" Ready="NotificationCenter_Ready" ZumoKey="YOUR_KEY" AbsentNotificationsColor="White"></ht:NotificationCenter>

And there you go:

wp_ss_20131103_0001

You can download the source code for the control here (ZIP file).

 

Moving Beem from Static XML to Azure Mobile Services

While working on Beem, I always relied on a static XML file to fetch me the list of available online radio streams. It’s a good way to keep the content dynamic, and when new stations are added, I do not have to re-submit the application for certification but rather just update the XML file. This worked fine for a while, but I thought about having a more optimal way to get the necessary data. Specifically, I wanted to have a backend that can be easily extended and modified when necessary. Relying on an XML file means that I am restricted to the static data set, that has to be re-downloaded all the time whenever something from it is needed.

The switch was done in favor of Azure Mobile Services. That way, I can go as far as run LINQ queries on my data and get a JSON-formatted output exactly for what I need, if I am building a companion app for Windows Phone 8 or Windows 8. More than that, the data can be easily updated directly from the mobile device, without having the XML file downloaded in its entirety. And while it is not that large, on devices with limited data this is a consideration I have to throw into the play.

Let’s start with the fact that there is no Azure Mobile Services SDK for Windows Phone 7.5 applications. If the application would be designed for Windows Phone 8, you can download the official toolset. Beem supports multiple devices that are running Windows Phone OS 7.5, therefore I cannot do the full switch to Windows Phone OS 8. Does this mean that I cannot use AMS? No. There are some trade-offs, such as the fact that I no longer have SDK-based access to LINQ queries out-of-the-box, but I can still access the information in the way I want due to the fact that AMS sports a REST API. So all I needed is implement my own client class.

My recommendation would also be to use JSON.NET in your application, since the data returned by HTTP requests will be JSON-formatted, but you can also parse JSON manually (who would reinvent the wheel anyway?) if you want to. Getting back to the application, I am operating on a Station model:

 using System;
using System.Windows;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Beem.Models
{
public class Station : INotifyPropertyChanged
{
// Required by Azure Mobile Services
// As we transition from XML to ZUMO, this will be persistent.
[XmlIgnore()]
public int? Id { get; set; }

[XmlElement("name")]
public string Name { get; set; }
[XmlElement("location")]
public string Location { get; set; }
[XmlElement("image")]
public string Image { get; set; }
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("jsonid")]
public string JSONID { get; set; }

private ObservableCollection<Track> _trackList;
[XmlIgnore()]
public ObservableCollection<Track> TrackList
{
get
{
return _trackList;
}
set
{
if (_trackList != value)
{
_trackList = value;
NotifyPropertyChanged("TrackList");
}
}
}

private Track _nowPlaying;
public Track NowPlaying
{
get
{
return _nowPlaying;
}
set
{
if (_nowPlaying != value)
{
_nowPlaying = value;
NotifyPropertyChanged("NowPlaying");
}
}
}

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
Deployment.Current.Dispatcher.BeginInvoke(() => { PropertyChanged(this, new PropertyChangedEventArgs(info)); });
}
}
}
}

What we need to have in the AMS storage is a reference to the station ID, name, description, stream URL (location), image URL and the JSON ID that will be subsequently used to query the currently playing track, as well as the previously played content. One way to do this is enable dynamic schema, where I can push items into the store, but I can also access the server through SQL Server Management Studio.

You can get the connection information in the Azure Management Portal.

Back to the fact that I needed to implement my own connection client. Here is what I did:

 public class MobileServicesClient
{
private const string CORE_URL = "https://beem.azure-mobile.net/tables/";
private const string KEY = "";

private WebClient client;

public MobileServicesClient()
{
client = new WebClient();
client.Headers["X-ZUMO-APPLICATION"] = KEY;
client.Headers["Accept"] = "application/json";
}

public void GetAllStations(Action<IEnumerable<Station>> onCompletion)
{
client.DownloadStringCompleted += (s, e) =>
{
IEnumerable<Station> stations = JsonConvert.DeserializeObject<IEnumerable<Station>>(e.Result);
onCompletion(stations);
};
client.DownloadStringAsync(new Uri(string.Concat(CORE_URL, "Station")));
}

public void AddStation(Station station, Action onCompletion)
{
var serializedObject = JsonConvert.SerializeObject(station,
new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
string content = serializedObject;

client.Headers[HttpRequestHeader.ContentType] = "application/json";
client.Headers[HttpRequestHeader.ContentLength] = content.Length.ToString();

var uri = new Uri(string.Concat(CORE_URL, "Station"));

client.UploadStringCompleted += (s, e) => {
onCompletion();
};
client.UploadStringAsync(uri, content);
}
}

In this class, I need to define the connection API key (once again, obtained in the Azure Management Portal). Remember that depending on the actions that you will take on the database, you need to make sure that the operation is permitted for the given key.

The requests against the store are performed with the help of a WebClient class, where I am also setting the proper authentication (X-ZUME-APPLICATION) and content headers. When I am simply trying to get the data, the only part that I need to add to the core URL is the table name – in my case, Station. When the response is received, I can deserialize it as IEnumerable<Station> and then bind it anywhere in the application, which ultimately results in this:

2 of 6

Adding data is done with the help of AddStation. The process is pretty much the opposite of how the data retrieval happens. I am serializing a station to JSON and calling UploadStringAsync to perform a POST request. Easy and effective – the new release of Beem will be running on top of AMS.