In our chat at work, we have it set up so that whenever somebody does a deploy, it will mention who did the deploy and when. Usually, i don’t care about it. I only care when i’m at my computer, so i disabled all those notifications to reduce the noise. However, the chat app has this backlog of messages that are marked unread for me. It bothered me that they i had to go in and actively acknowledge reading them to get my “needs your attention” count down to zero.
So i created a little app (two actually) that displayed toast native Windows 8 notifications when these events happened. If i was at my computer, i’d see them; if i wasn’t around, then they didn’t affect me at all and i don’t care. The result looks something like this:
I created a super simple WebAPI / SignalR service that sits out in the cloud. When it receives a POST, it shoots that back out to all those connected via SignalR. Then i have a little app that runs as a windows service, connects to the SignalR hub, and generates a toast when something happens. Since a notification is just a person performing some action, the class is pretty straightforward:
public class NotificationMessage
{
/// <summary> The person that did something </summary>
public string Person { get; private set; }
/// <summary> The event that happened </summary>
public string Action { get; private set; }
public NotificationMessage(String person, String action)
{
this.Person = person;
this.Action = action;
}
public override string ToString()
{
return this.Person + " " + this.Action;
}
}
The WebAPI / SignalR service
First, the cloud service. It has among the simplest WebAPI controllers around:
public class NotificationsController : ApiController
{
// when a notification is posted to this endpoint,
// fire it off to any that might be listening on the hub
public void Post([FromBody] NotificationMessage notification)
{
if (notification == null)
return;
IHubContext notificationHub = GlobalHost.ConnectionManager.GetHubContext();
notificationHub.Clients.All.notify(notification);
}
}
So all this controller does is take a notification and, as long as it’s not null, calls notify
on all those connected to the hub. Here’s the hub:
public class NotificationsHub : Hub
{
/// <summary> Called by the client to ensure connectivity </summary>
public void Heartbeat() { }
}
Because no client ever calls notify
, i don't even need to have a method for it. There is one though, Heartbeat
, which i'll get to later.
That’s it for the cloud service – just those two main things.
The Windows Service
Fortunately to get started, there’s a good really sample that does notifications from a desktop app here. Toast notifications are just XML and they have predefined templates. Some templates have pictures and they specify how the text will be displayed. I’m using ToastText02 because it has one main line that’s bold and then subsequent lines are just one string that wraps – without a picture displayed alongside. Dissecting the sample, i got the core functionality of showing a toast of one of the NotificationMessage
s:
// Get a toast XML template
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
// Fill in the text elements
XmlNodeList stringElements = toastXml.GetElementsByTagName("text");
if (newNotification.Person != null)
stringElements[0].AppendChild(toastXml.CreateTextNode(newNotification.Person));
if (newNotification.Action != null)
stringElements[1].AppendChild(toastXml.CreateTextNode(newNotification.Action));
// Create the toast and show it
ToastNotification toast = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier(_appID).Show(toast);
There’s two tricky bits here. One is that you have to have a shortcut to your app on your start screen (not necessarily pinned though), and the other is that you need to reference some library named Windows.winmd
. The sample includes code to add the shortcut automatically, so that shouldn’t be an issue. But to add the reference to that library, i had to edit the project file manually and add in the reference:
<reference include="Windows">
<hintpath>C:\Program Files (x86)\Windows Kits\8.0\References\CommonConfiguration\Neutral\Windows.winmd</hintpath>
</reference>
After that it was as simple as wiring it up to a the SignalR hub and listening for messages:
public async Task StartConnection()
{
try
{
//create the connection
_hubConnection = new HubConnection("http://<my_webapi_site_url>/");
_proxy = _hubConnection.CreateHubProxy("NotificationsHub");
//when we get "notify", call AddNotification
_proxy.On<NotificationMessage>("notify", AddNotification);
//connect
await _hubConnection.Start();
//start the timer if it's not started
if (!_heartbeatTimer.Enabled)
_heartbeatTimer.Start();
Logger.Log("Connection started");
}
catch (Exception) { }
}
If you notice, i have something called _heartbeatTimer
. I had an issue where the client wouldn’t detect a loss of connection to the SignalR hub. I would turn off my wireless antenna and the service would still think it was connected. Because of that, once i turned my antenna back on, it wouldn’t attempt a reconnect. After spending too much time on it, i got around this in an incredibly hacky way. Every thirty seconds or so, the service calls the Heartbeat
method on the hub. If it fails or times out for any reason, i attempt to reconnect. It will continue to do reconnect attemps every thirty seconds as long as the call to the hub fails.
private async void CheckHeartbeat(object sender, ElapsedEventArgs e)
{
//don't do anything if we're attempting to connect
if (_hubConnection.State == ConnectionState.Connecting ||
_hubConnection.State == ConnectionState.Reconnecting)
return;
try
{
//try to contact the server
await _proxy.Invoke("Heartbeat");
}
catch (Exception)
{
//failure, so toss the current connection in the garbage
_hubConnection.Dispose();
//try to reconnect
this.StartConnection();
}
}
After that, i just had to wire up the code to use it as a windows service, install it, and then i can invoke notifications via a simple curl.
curl -d '{person:"sam",action:"ate all my potato chips"}' -H Content-Type:application/json -s http://<my_webapi_site_url>/Notifications
We modified our build script at work to do invoke the curl when somebody triggers an action, and then if i’m around, i’ll see the notification on my computer.
WebAPI / SignalR service available on github.
Windows service available on github.
No comments:
Post a Comment