In this article I demonstrate how to send push notifications to Windows 8 client machines when an event occurs in SharePoint 2010. You also can use the pattern to send notifications to Windows Phone apps. Windows Push Notification Services (WNS) is an offering of the Windows Azure platform. This article focuses on using push notifications in a specific business scenario described herein; however, this same pattern can be applied to many different situations.

Scenario

A company uses a SharePoint list (named RFQs) to track requests for quotations (RFQs) that come from potential customers via the company's public, SharePoint 2010–based website. The list is integrated with a set of business processes that facilitate responding to the RFQs. The business process includes creating an estimate for the RFQ, creating a formal statement of work (SOW) that includes the estimate, approving the SOW, and submitting the SOW to the potential customer. Employees in the company use Windows 8 applications to track the RFQs and create estimates for them. As the RFQ response process takes place, the SharePoint list is updated and employees receive notifications on their Windows 8 desktops and tablets. The notifications inform employees about the status of the process for each RFQ and any action items the employee needs to act on.

In particular, this article demonstrates how to send notifications to a Windows 8 application when a new RFQ is submitted by a potential customer, and how to update the RFQs list when an estimate is created for the RFQ via the Windows 8 application.

Components

The architecture necessary to create a push notification business process for a SharePoint 2010 website application comprises four components:

  • SharePoint 2010 Server—Monitors list item changes and relays them to a service such as an ASP.NET MVC 4 application.
  • ASP.NET MVC 4 Application—Receives events from SharePoint and packages and relays to Windows Push Notification Services.
  • Windows Push Notification Services (WNS)—Receives notification requests and forwards to subscribers.
  • Windows 8 Application—Shows notifications received from WNS.

Figure 1 illustrates the components that make up the solution.

Figure 1: Components Used to Send Push Notifications from SharePoint to Windows 8 Apps

The Windows 8 portion on the left of the diagram represents a computer running Windows 8. The built-in Notification Client Platform in the Windows 8 operating system receives notifications and relays them to Windows 8 applications.

On the SharePoint server, a list item event receiver attached to the SharePoint RFQs list executes when an item in the list is created or updated. The list item event receiver initiates sending toast and tile notifications to the Windows 8 application by calling into a local or cloud service that, in turn, calls the Windows Push Notification Service (WNS). The WNS then transmits the notifications to the Windows 8 application.

Although this example uses a list item event receiver to initiate the transmission of notifications, you also can use a timer job or custom code invoked by a web part or other component. For example, you can call the ASP.NET MVC 4 service to send a notification from a workflow.

The intermediate service between the list item event receiver and WNS is required because the WNS API libraries are compiled with the .NET 4 Framework and therefore can't be referenced by any assemblies deployed to SharePoint. This means the WNS libraries can't be referenced and called directly from the list item event receiver.

To work around this limitation, I employ an ASP.NET MVC 4 service. The ASP.NET MVC 4 service uses WNS libraries, so it's a good fit. You also can develop the service on other technologies that support WNS libraries (e.g., ASP.NET Web API). The ASP.NET MVC 4 (or other) Service can be hosted either locally (on premises) or in the cloud—it's your choice.

Authentication

Keep in mind that in this scenario, not only does the Windows 8 system receive notifications when data changes in SharePoint, but employees also browse RFQs and create estimates with the Windows 8 application. This means the Windows 8 app must authenticate to the SharePoint server before it can query the RFQs list.

The Windows 8 application uses one of two approaches to authenticate to the SharePoint server, depending on how the user logged on to the Windows 8 computer and how that person is connected to the network to which the SharePoint 2010 Server belongs. When the Windows 8 application runs from a computer where the user is logged in with domain credentials and connected to the domain network, authentication credentials are passed automatically to the SharePoint server. When the Windows 8 application runs from a computer where the user is not logged in with a domain account nor connected to the domain network, the user is prompted for credentials by the Windows 8 application. No special code is needed to facilitate this approach. The Windows 8 application automatically prompts for credentials when necessary.

Notifications

Now let's return to the beginning of the process, when a new list item is added to the RFQs, and examine the code for sending notifications. First, the list item event receiver creates URL(s) to call the ASP.NET MVC 4 service that, in turn, will call WNS to transmit the notification(s). These URLs contain information the ASP.NET MVC 4 service needs to determine what type of notification is being sent and what data is included in the notification. Depending on the change to the RFQs list, the list item event receiver creates the appropriate URLs to transmit dynamic toast or tile notifications to the Windows 8 app. For example, when a new item is added to the RFQs list, the code in Listings 1, 2, and 3 executes in the ItemAdded method for the list item event receiver. This code creates one toast notification and three tile notifications.

Note that the ItemUpdated event in the list item event receiver follows the same pattern to send notifications; however, it isn't shown in this article. The ItemUpdated event evaluates the status column in the RFQs list and sends notifications to update employees on the status of an RFQ. The ItemUpdated event also sends updated sales information once a customer accepts the terms in an SOW. I point this out to illustrate that you can implement notifications for many different types of events that occur in SharePoint lists.

Toast notification. The first type of notification is a toast notification (Figure 2).

Figure 2: Toast Notification

It notifies an employee to start a task (in this case, create an estimate). Toast notifications appear on the Windows 8 Start screen. Listing 1 shows code that generates a URL to send a toast notification via an ASP.NET MVC 4 service.

Listing 1: Code Generating Toast Notification via an ASP.NET MVC 4 Service

string toastTitle = Convert.ToString(properties.AfterProperties["Title"]);
string company = Convert.ToString(properties.AfterProperties["Company"]);
string toast = string.Format("Please create the estimate for the {0}
      project for company {1}.", toastTitle, company);
string url = PostUrl + string.Format("?itemTitle={0}&notType={1}",
      Util.HttpEncode(toast), NotificationType.Toast);

Util.PostData(url);

Tile notification. The second type of notification is a tile notification. Figure 3 shows three tile notifications.

Figure 3: Tile Notifications

Look at the first tile, which informs employees that a new sales lead (RFQ) is in the system. The code that generates this tile notification is shown in Listing 2.

Listing 2: Code Generating New Sales Lead Tile Notification via an ASP.NET MVC 4 Service

string tileTitle = "A new sales lead has been entered into the system";
url = PostUrl + string.Format("?itemTitle={0}&notType={1}&tileType={2}&imageUrl={3}",
          Util.HttpEncode(tileTitle), NotificationType.Tile, TileType.TileWideText03,
          Util.HttpEncode(properties.WebUrl + "/Shared%20Documents/star.jpg"));
Util.PostData(url);

Tile notifications appear on the tile corresponding to the Windows 8 application that receives them, on the Windows 8 Start screen.

The center tile notification in Figure 3 provides employees with up-to-the-minute sales information. The code that generates this tile notification accesses an Excel file stored on the SharePoint server and retrieves annual revenues (Listing 3).

Listing 3: Code Generating Yearly Revenue Tile Notification via an ASP.NET MVC 4 Service

tileTitle = "Yearly Rev";
string imageUrl = properties.WebUrl + Util.GetYearlyRevImage(company);
url = PostUrl +
string.Format("?itemTitle={0}&notType={1}" +
      "&tileType={2}&imageUrl={3}&cell1={4}" +
      "&cell2={5}&cell3={6}", Util.HttpEncode(tileTitle), NotificationType.Tile,
      TileType.TileWideImage, Util.HttpEncode(imageUrl),
      Util.GetACellValueFromExcel(company, "2010", web),
      Util.GetACellValueFromExcel(company, "2011", web),
      Util.GetACellValueFromExcel(company, "2012", web));
Util.PostData(url);

The code that generates the bottom tile notification in Figure 3 accesses an Excel file stored on the SharePoint server and retrieves quarterly revenue sales information (Listing 4).

Listing 4: Code Generating Quarterly Revenue Tile Notification via an ASP.NET MVC 4 Service

tileTitle = "Quarterly Rev";
imageUrl = properties.WebUrl + Util.GetQuarterlyRevImage(company);
url = PostUrl +
      string.Format("?itemTitle={0}&notType={1}" +
      "&tileType={2}&imageUrl={3}&cell1={4}&cell2={5}&cell3={6}",
      Util.HttpEncode(tileTitle), NotificationType.Tile,
      TileType.TileWideSmallImageAndText01, Util.HttpEncode(imageUrl),
      Util.GetACellValueFromExcel(company, "2011 Q4", web),
      Util.GetACellValueFromExcel(company, "2012 Q1", web),
      Util.GetACellValueFromExcel(company, "2012 Q2", web));
Util.PostData(url);

HttpWebRequest.You might notice the line of code containing Util.PostData(url); in Listings 1, 2, 3, and 4. To call the ASP.NET MVC 4 service and transmit the notification to WNS, the SharePoint list item event receiver makes an HttpWebRequest inside this method (Listing 5).

Listing 5: Calling ASP.NET MVC 4 Service from List Item Event Receiver

public static void PostData(string purl)
{
    try
    {
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(purl);
        req.Timeout = 90000;
        req.Method = "Post";
        req.ContentType = "application/x-www-form-urlencoded";
        req.ContentLength = 0;
        HttpWebResponse rep = (HttpWebResponse)req.GetResponse();
    }
    catch (Exception ex)
    {

    }
}

When the ASP.NET MVC 4 service receives the request, it inspects the URL to determine what type of notification to send and then calls the appropriate code to send the notification to WNS (Listing 6).

Note that before you can use WNS, you must register your application and receive the credentials for your service. The process for registering an application with the Windows Store Dashboard and other helpful instructions are documented on MSDN. Keep an eye on my blog in coming weeks for links to MSDN code samples that illustrate this pattern for SharePoint 2013 as well.

As I walk through the code examples, you'll see that this is a relatively straightforward pattern—the code isn't complicated. The biggest challenge is understanding and overcoming the limitations associated with the WNS libraries, which I discuss above.

Depending on the type of notification that is applicable, one of two methods in the ASP.NET MVC 4 service is called to send a notification to WNS. Both methods follow the pattern documented on the MSDN website. Let's examine them in-depth.

SendToastNotification. The SendToastNotification (Listing 7) method sends toast notifications. The first thing it does is call the GetAccessToken Method. GetAccessToken sends the client ID and secret to the Windows Live ID Service and returns an access token that accesses WNS. The client ID and secret are provided when you register your application on the Windows Store Dashboard.

The channelUrl variable (Listing 7) corresponds to the channel you set up with WNS. The channel lets the application (in this case, the ASP.NET MVC 4 service) interact with WNS.

The data variable defines part of the payload that is sent to WNS via a POST HttpWebRequest (Listing 7). The data variable is combined with the title and converted to a byte array before the payload is sent to WNS. WNS uses the payload value to transmit the notifications to the Windows 8 application. Notice the access token is added to the request headers to authenticate the request.

SendTileNotification. The SendTileNotification (Listing 8) method sends tile notifications. It follows the same pattern as the SendToastNotification method, except it sends tile notifications. Notice this method supports sending multiple tile notifications from a single entry point. The tileNotificationType parameter defines which tile notification is sent.

The cell1, cell2, and cell3 variables in Listing 8 represent data from the Excel workbook that was sent via the URL that the list item event receiver assembled to call the ASP.NET MVC 4 service. These values are part of the tile notifications in Figure 3 that include financial data.

Finally, the imageUrl variable in Listing 8 includes the path to an image on the SharePoint server that represents the chart icon that appears on the tile notifications in Figure 3.

Queries

Now that I've shown how to send notifications, let's loop back to querying SharePoint data from a Windows 8 application. When the estimate for the new sales lead is created in the Windows 8 application, the total value for the estimate is saved in the revenue column of the RFQs list, and the LeadStatusValue column is updated with an estimate created status. This causes the ItemUpdated method in the list item event receiver to execute, which in turn sends notifications via WNS to employees, letting them know this part of the RFQ process is complete. The Windows 8 application uses the SharePoint 2010 ListData Representational State Transfer (REST) API to update column values. Listing 9 demonstrates how this API is invoked from JavaScript in the Windows 8 application.

You also can perform other operations with the SharePoint 2010 ListData REST API to perform a full complement of create, read, update, and delete (CRUD) operations on SharePoint lists from Windows 8 applications.

Notifications Drive Efficient Business

You can combine SharePoint, Windows 8, and WNS to create next-generation line of business applications that keep end users apprised of up-to-date information and help drive business processes. The notifications can go to an end user's desktop or mobile device (via Windows Phone apps) for greater flexibility. This article presents a scenario for notifying employees about the status of RFQs and tasks that need to be completed throughout the process, but the pattern can be applied to many different situations.

Listing 6: Evaluating Notification Types and Preparing to Call WNS

 [HttpPost]
public ActionResult SendNotification()
{
    try
    {
               
    ChannelURL = Util.GetConfigValue("channelUrl");

    NotificationType notificationType =
        Util.GetNotificationType(Convert.ToString(Request["notType"]));

    string title =Util.HttpDecode(Convert.ToString(Request["itemTitle"]));                

    if (notificationType == NotificationType.Tile)
    {
        TileType tileType = Util.GetTileType(Convert.ToString(Request["tileType"]));
        string imageUrl = Convert.ToString(Request["imageUrl"]);
        Util.SendTileNotification(title, imageUrl, ChannelURL, tileType);
    }
    else
    {
        Util.SendToastNotification(title, ChannelURL);              
    }
    }
    catch
    { }
    return new EmptyResult();
}

Listing 7: Sending Toast Notifications via WNS

public static void SendToastNotification(string title, string channelUrl)
{

    string accessToken = GetAccessToken();
    var subscriptionUri = new Uri(channelUrl);

    var request = (HttpWebRequest)WebRequest.Create(subscriptionUri);
    request.Method = "POST";
    request.ContentType = "text/xml";
    request.Headers = new WebHeaderCollection();
    request.Headers.Add("X-WNS-Type", "wns/toast");
    request.Headers.Add("Authorization", "Bearer " + accessToken);

    string data = "<toast><visual version='1' lang='en-US'>}" +
                  "<binding template='ToastText01'>}" +
                  "<text id='1'>{0}</text></binding></visual></toast>";

    byte[] notificationMessage = Encoding.Default.GetBytes(string.Format(data, title));
    request.ContentLength = notificationMessage.Length;
    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(notificationMessage, 0, notificationMessage.Length);
    }

     var response = (HttpWebResponse)request.GetResponse();
}

Listing 8: Sending Tile Notifications via WNS


public static void SendTileNotification(string title, string imageUrl, string channelUrl,
                                        TileType tileNotificationType)
{

var subscriptionUri = new Uri(channelUrl);
string accessToken = GetAccessToken();
var request = (HttpWebRequest)WebRequest.Create(subscriptionUri);
request.Method = "POST";
request.ContentType = "text/xml";
request.Headers = new WebHeaderCollection();
request.Headers.Add("X-WNS-Type", "wns/tile");

string data = string.Empty;

if (tileNotificationType == TileType.TileWideText03)
{
    request.Headers.Add("X-WNS-Tag", "aa11");
    data = string.Format("<?xml version='1.0' encoding='utf-8'?>" +
                         "<tile><visual lang=\"en-US\">" +
                         "<binding template=\"TileWideText04\">" +
                         "<text id=\"1\">{0}</text>" +
                         "</binding>" +
                         "</visual></tile>", title);
}

string cell1 = Convert.ToString(System.Web.HttpContext.Current.Request["cell1"]);
string cell2 = Convert.ToString(System.Web.HttpContext.Current.Request["cell2"]);
string cell3 = Convert.ToString(System.Web.HttpContext.Current.Request["cell3"]);

if (tileNotificationType == TileType.TileWideImage)
{
    request.Headers.Add("X-WNS-Tag", "aa22");
    imageUrl = "<SharePoint Server URL>/img/chart.png";
    data = string.Format("<?xml version='1.0' encoding='utf-8'?>" +
                            "<tile><visual>" +
                             "<binding template=\"TileWideSmallImageAndText02\">" +
                             "<image id=\"1\" src=\"{0}\" alt=\"{1}\"/>" +
                             "<text id=\"1\">{1}</text>" +
                             "<text id=\"2\">2010: ${2}</text>" +
                             "<text id=\"3\">2011: ${3}</text>" +
                             "<text id=\"4\">2012: ${4}</text>" +
                             "<text id=\"5\"></text>" +
                             "</binding>" +
                             "</visual></tile>", imageUrl, title, cell1, cell2, cell3);
    }

    if (tileNotificationType == TileType.TileWideSmallImageAndText01)
    {
        request.Headers.Add("X-WNS-Tag", "aa33");
        imageUrl = "http://<SharePoint Server URL>/img/chart.png";
        data = string.Format("<?xml version='1.0' encoding='utf-8'?> " +
                             "<tile><visual>" +
                             "<binding template=\"TileWideSmallImageAndText02\">" +
                             "<image id=\"1\" src=\"{0}\" alt=\"{1}\"/>" +
                             "<text id=\"1\">{1}</text>" +
                             "<text id=\"2\">2011 Q4: ${2}</text>" +
                             "<text id=\"3\">2012 Q1: ${3}</text>" +
                             "<text id=\"4\">2012 Q2: ${4}</text>" +
                             "<text id=\"5\"></text>" +
                             "</binding>" +
                             "</visual></tile>", imageUrl, title, cell1, cell2, cell3);
    }

    request.Headers.Add("X-WNS-TTL", "600000");
    request.Headers.Add("Authorization", "Bearer " + accessToken);
    byte[] notificationMessage = Encoding.Default.GetBytes(data);
    request.ContentLength = notificationMessage.Length;

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(notificationMessage, 0, notificationMessage.Length);

    }

    var response = (HttpWebResponse)request.GetResponse();

}

Listing 9: Using the SharePoint ListData API to Update a SharePoint List Term

Listing 9: Using the SharePoint ListData API to Update a SharePoint List Item
function putSharePointData(item, handle) {
        var spListItemRestSrvUri =
              WinJS.Resources.getString("intranet.contoso.com").value +
              "/_vti_bin/listdata.svc/RFQs(" + item.Id + ")",
              spItem = {
                  Revenue: item.Revenue,
                  LeadStatusValue: "Estimate Created"
              },
              etag = item._etag;

        WinJS.xhr({
            url: spListItemRestSrvUri,
            type: "POST",
            data: JSON.stringify(spItem),
            headers: {
                     accept: 'application/json',
                     "Content-Type": 'application/json; charset=utf-8',
                     "If-Match": etag,
                     "X-HTTP-Method" : "MERGE" }
        }).
        then(
    function complete(result) {
        handle();
    },
    function error(error) {
    logger.log("Not able to put data to SharePoint", "error",
               "spData.js", error.status, error.statusText);
    });
}