So far in the preceding walkthroughs, you have used a
somewhat unrealistic approach to communicating push notification URLs
from the Windows Phone 7 client application to the push notification
server. You copied that URL from the Debug window of the client
application and pasted it into the server application, where it was used
to send tiles, toasts, and raw notifications to the Windows Phone 7
applications. To make the stock alerts application a bit more
real-world, however, you must automate the URL communication piece. In
this section, you will learn how to do that using a cloud service built
with the Microsoft Windows Communication Foundation (WCF) stack of
technologies.
1. Creating a WCF Service to Track Notification Recipients
In this section, we will show
you how to enhance the PNServer application built previously by adding a
WCF service to it. WCF is a very powerful technology with an array of
configuration options for creating and hosting cloud services. You will
be building what is known as a self-hosted service,
which means that it will be hosted within the Windows Forms application
and you will write code to initialize and start that service. Another
important point about this service is that it will be a RESTful service,
which, for our purposes right now, means that you can access operations
of the service over the properly formatted URLs, as you will see
shortly.
Before you create a RESTful
WCF service, however, you may need to make a small change in the Visual
Studio environment to reference assemblies you need to create that
service. The reason for this is that, by default, Visual Studio creates a
lightweight profile for client applications, such as Windows Forms or
Windows Presentation Foundation (WPF) applications. This lightweight
profile omits many web-related assemblies by default because the chances
of a true client application needing them are slim.
The setting that controls which
assemblies are included or left out is the Target Framework setting,
and it is located on your project's Properties page. You need to change
this setting from ".Net Framework 4 Client Profile" to ".Net Framework
4." To accomplish that, open the PNServer project if it's not already
open, right-click the project name, and then select Properties. Locate
the Target Framework setting and set it to ".Net Framework 4," as
illustrated in Figure 17-9.
Now follow these steps to complete creation of the WCF service. First, before creating the service, you need to include the System.ServiceModel.Web assembly to the PNServer project.
Right-click the project name and select Add Reference. Locate the System.ServiceModel.Web assembly in the list, highlight it, and click OK.
Now, you will add WCF
service files to the project. Adding the WCF service files will consist
of two parts: creating what is known as a Service Contract,
which will appear in the form of an Interface file, and defining a
class that will physically implement the methods defined within the
Service Contract.
To create the Service Contract, right-click the project name, choose Add=>New
Item, and then scroll almost all the way to the bottom and pick WCF
Service. Name the service "Registration Service," and then click OK.
Add the following statement to the top of the IRegistrationService.cs file created:
using System.ServiceModel.Web;
Add the following code to the IRegistrationService.cs file:
[ServiceContract]
public interface IRegistrationService
{
[OperationContract, WebGet]
void Register(string uri);
[OperationContract, WebGet]
void Unregister(string uri);
}
Note how you defined two
operations for the service to perform: Register new Windows Phone 7
clients for push notifications and Unregister them.
Now it's time to add the implementation of the Register and Unregister methods.
Double-click the RegistrationService.cs file that Visual Studio added to your project. Make the RegistrationService.cs file look like the code here.
public class RegistrationService : IRegistrationService
{
private static List<Uri> subscribers = new List<Uri>();
private static object obj = new object();
public void Register(string uri)
{
Uri channelUri = new Uri(uri, UriKind.Absolute);
Subscribe(channelUri);
}
public void Unregister(string uri)
{
Uri channelUri = new Uri(uri, UriKind.Absolute);
Unsubscribe(channelUri);
}
private void Subscribe(Uri channelUri)
{
lock (obj)
{
if (!subscribers.Exists((u) => u == channelUri))
{
subscribers.Add(channelUri);
}
}
}
public static void Unsubscribe(Uri channelUri)
{
lock (obj)
{
subscribers.Remove(channelUri);
}
}
public static List<Uri> GetSubscribers()
{
return subscribers;
}
}
Take a look closer look at the code that you just added to the RegistrationService.cs file. Notice that the RegistrationService class implements the IRegistrationService
interface on the very first line—this is important! Aside from that,
the code is pretty straightforward: a collection of push notification
URIs is maintained in the static subscribers variable, and every client that calls the Register method of the service gets added to that list of subscribers. The lock
function is used to prevent multiple clients changing the same data at
the same exact moment in time, possibly resulting in incomplete and
unpredictable data.
In the beginning of this section,
we said that a WCF service hosted by a Windows Forms application needs
initialization code to start up. One of the places this initialization
code can go is in the load event of Form1.
Here's the code you need to start up the service. Copy it to the load event of Form1 here:
ServiceHost host;
host = new ServiceHost(typeof(RegistrationService));
host.Open();
You are almost done—now you need only to provide some configuration parameters for the WCF service to run.
Open the app.config file and add the following configuration parameters to the <system.ServiceModel> element (you should already have configuration settings defined within <system.ServiceModel>, but now you need to make sure those settings match precisely what is pasted here):
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="EndpointPNServerServiceBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="PNServer.RegistrationService">
<endpoint address="http://localhost/RegistrationService"
behaviorConfiguration="EndpointPNServerServiceBehavior"
binding="webHttpBinding"
contract="WP7_Push_Notifications.IRegistrationService">
</endpoint>
</service>
</services>
</system.serviceModel>
In a nutshell, with these settings you have configured your service to listen at the following address: http://localhost/RegistrationService. You have also specified that the requests to this service will be coming over the http protocol.
Finally, you will
modify the main application form (Form1) and add a Broadcast button that
will send a push notification to all subscribed clients. Once clicked,
the button click handler will get a list of all clients subscribed and
send each one of them a push notification (toast notification in the
following code). Here's how to do that.
Open Form1.cs in Design view and add a button to that form underneath the "Send Notification" button.
Change the button's text to "Broadcast," as shown in Figure 2.
Change the button's name to "btnBroadcast," double-click it, and make sure that the button's Click event contains the following code:
private void btnBroadcast_Click(object sender, EventArgs e)
{
if (txtTitle.Text == string.Empty || txtText.Text == string.Empty)
{
MessageBox.Show("Please enter text and title to send");
return;
}
List<Uri> allSubscribersUri = RegistrationService.GetSubscribers();
foreach (Uri subscriberUri in allSubscribersUri)
{
sendPushNotificationToClient(subscriberUri.ToString());
}
}
Add the following code to the sendPushNotificationToClient function:
private void sendPushNotificationToClient(string url)
{
HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(url);
sendNotificationRequest.Method = "POST";
sendNotificationRequest.Headers = new WebHeaderCollection();
sendNotificationRequest.ContentType = "text/xml";
sendNotificationRequest.Headers.Add("X-WindowsPhone-Target", "toast");
sendNotificationRequest.Headers.Add("X-NotificationClass", "2");
string str = string.Format(TilePushXML, txtTitle.Text, txtText.Text);
byte[] strBytes = new UTF8Encoding().GetBytes(str);
sendNotificationRequest.ContentLength = strBytes.Length;
using (Stream requestStream = sendNotificationRequest.GetRequestStream())
{
requestStream.Write(strBytes, 0, strBytes.Length);
}
try
{
HttpWebResponse response =
(HttpWebResponse)sendNotificationRequest.GetResponse();
string notificationStatus = response.Headers["X-NotificationStatus"];
string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];
lblStatus.Text = "Status: " + notificationStatus + " : " +
deviceConnectionStatus;
}
catch (Exception ex)
{
//handle 404 (URI not found) and other exceptions that may occur
lblStatus.Text = "Failed to connect, exception detail: " + ex.Message;
}
Note that the TilePushXML
variable has been previously defined when we talked about Tile
Notifications—specifically, in the Creating an Application to Send
Notifications section. With the WCF service tracking subscribed clients
and sending push notifications complete, it is now time to enhance the
client application to call the web service with its push notification
URL.
2. Modifying the Client to Call the WCF Service
The Windows Phone 7 Push
Notification client application needs to be modified to call the newly
implemented web service with the push notification URL. Previously, we
briefly mentioned that the convenience of creating a RESTful WCF service
lies in the fact that the operations of that web service can be
accessed as URLs. For instance, the URL http://localhost/RegistrationService/Register?uri={0}accesses the Register function of the web service created in the previous section; the uri
parameter is supplied on the QueryString. With that in mind, you can go
ahead and complete the Windows Phone 7 Push Notification client
implementation by creating the functions that will register/unregister a
Windows Phone 7 client with the server:
Launch Visual Studio 2010 Express for Windows Phone and open the PNClient project.
Locate the ChannelUri property getter and setter and change them to the following (notice the use of two new functions, RegisterUriWithServer and UnregisterUriFromServer).
public Uri ChannelUri
{
get { return channelUri; }
set
{
//unregister the old URI from the server
if (channelUri!=null)
UnregisterUriFromServer(channelUri);
//register the new URI with the server
RegisterUriWithServer(value);
channelUri = value;
OnChannelUriChanged(value);
}
}
Now
add the following two functions to invoke the WCF service that you have
created (note that when it comes time to release your service to
production, you will be most likely deploying this service somewhere in
the cloud).
private void RegisterUriWithServer(Uri newChannelUri)
{
//Hardcode for solution - need to be updated in case the REST WCF service addresschange
string baseUri = "http://localhost/RegistrationService/Register?uri={0}";
string theUri = String.Format(baseUri, newChannelUri.ToString());
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
if (e.Error == null)
Dispatcher.BeginInvoke(() => {
txtURI.Text = "changing uri to " + newChannelUri.ToString();
});
else
Dispatcher.BeginInvoke(() =>
{
txtURI.Text = "registration failed " + e.Error.Message;
});
};
client.DownloadStringAsync(new Uri(theUri));
}
private void UnregisterUriFromServer(Uri oldChannelUri)
{
//Hardcode for solution - need to be updated in case the REST WCF service address change
string baseUri = "http://localhost/RegistrationService/Unregister?uri={0}";
string theUri = String.Format(baseUri, oldChannelUri.ToString());
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
if (e.Error == null)
Dispatcher.BeginInvoke(() =>
{
txtURI.Text = "unregistered uri " + oldChannelUri.ToString();
});
else
Dispatcher.BeginInvoke(() =>
{
txtURI.Text = "registration delete failed " + e.Error.Message;
});
};
client.DownloadStringAsync(new Uri(theUri));
}
In the preceding code, notice
that the URL of the cloud is hard-coded—this URL must match the URL you
have specified in the configuration file (app.config) for the WCF service. Notice also how the event handlers (client.DownloadStringCompleted) are wired up—those event handlers provide the status updates on whether the registration/unregistration succeeded or failed.
At this point, you
have completed writing both the server and the client piece for
automated push notification. It is now time to verify that the server is
able to keep track and notify its clients appropriately, without the
need to manually copy and paste the push notification URL.
3. Verifying Automated Push Notification Subscriber Tracking
To test automated push
notification tracking, the very first thing you have to do is make sure
that the WCF service starts up appropriately and that it is able to
process requests coming in. Here's how:
WCF
Services are designed with security in mind and there are numerous
security configuration options for those services. To bypass security
configuration options so that you can test the service you built, you
will need to run the WCF Service project as Administrator. The quickest
way to accomplish that would be to exit Visual Studio, then right-click
on the shortcut to Visual Studio and choose "Run as Administrator"
option. Once Visual Studio comes up, open the PNServer solution. You are
now set to run PNServer as Administrator.
To verify that the WCF service is indeed ready to accept client connections, set a breakpoint at the first line of the Register function of the RegistrationService class, and then press F5 to start the PNServer application.
If the application is running and the Windows form shown in Figure 2 is displayed, then fire up Internet Explorer (or any other browser) and go to the following URL:
http://localhost/RegistrationService/Register?uri=http://www.microsoft.com
If the breakpoint gets hit
after you access this URL, this means that the service is running and it
is ready for clients to connect.
If the breakpoint does not get hit and you see a message that the page cannot be displayed, verify that the content in the <system.ServiceModel> section of yourapp.config
file in the PNServer project matches the content of that file described
in the section on creating a WCF service. Most likely, some sort of
configuration issue is preventing you from properly launching the
service.
Once you've confirmed that
the service is running, you can observe the automated push notification
subscriber tracking in action by following these steps:
Launch PNClient application and click the Create Channel button. If you still have the breakpoint set in the Register function of the WCF service, that breakpoint should be hit.
To
be able to see toast notifications on the phone, you need to pin the
application icon to the Start screen. To accomplish that, click the
phone's Windows button, and then click the arrow (=>) to open the Windows Phone 7 Options screen, as previously shown in this Figure. Click and hold the left mouse button (also referred to as a "long click") to bring up the pop-up menu shown in this Figure, and then click the Pin to Start option.
With
the application icon pinned onto the Start screen, you are ready to
receive notifications on the phone. In the PNServer application window,
enter the title and the text of the notification message to send and
press the Broadcast button. A second or two later, you should see the
push notification coming through to the phone.
With clients and cloud
service dynamically exchanging push notification URLs and clients
accepting push notifications, this is a good point to conclude push
notifications walkthroughs. The next sections will give you a
perspective on using push notifications in the real world and summarize
what you have learned in this article. The solution that you have built
in this article provides the "full lifecycle" implementation of Push
Notifications; however, it has a set of limitations that should be
considered before deploying it to production. Windows Phone 7 client
applications that go down do not unregister themselves from the server;
therefore, the server will try to send notifications to non-existent
channels. The server lacks persistency—all of the connected client
addresses are kept in-memory, which means that they all will be lost
should the service be shut down accidentally or on purpose. Finally,
there's no centralized scheduling or event-based mechanism for
distributing notifications: you have to push the button on the Windows
Forms application to distribute the notifications. In the real world,
the notifications will most likely be distributed in response to some
external events (such as Microsoft stock rising rapidly), and the
service has to be smart about handling those.