Registering for Notifications

0
204

Before a phone can receive notifications from the Surveys service of new surveys, it must obtain its unique URI from the MPNS. This registration process takes place when the user taps the Save button on the AppSettingsView page.

The following code example shows the IRegistrationService interface in the TailSpin.PhoneClient project that defines the registration operations that the mobile client can perform.

C#
public interface IRegistrationServiceClient
{
IObservable<TaskSummaryResult> UpdateReceiveNotifications(
bool receiveNotifications);
IObservable<Unit> UpdateTenants(
IEnumerable<TenantItem> tenants);
IObservable<SurveyFiltersInformation>
GetSurveysFilterInformation();
}

For details of the UpdateTenants and GetSurveysFilterInformation methods

The RegistrationServiceClient class implements the UpdateReceiveNotifications method to handle the registration process for the mobile client. The following code example shows how the UpdateReceiveNotifications method manages the HttpNotificationChannel object in the mobile client application:

  • The method first tries to retrieve previously cached channel information by calling the Find method.
  • If the user is enabling notifications from MPNS, the method removes any cached channel data before binding to the MPNS channel and registering the unique URI with the Tailspin Surveys service.
  • If the user is disabling notifications from MPNS, the method unregisters from the Tailspin Surveys service and removes the channel.

C#
private const string ChannelName = “tailspindemo.cloudapp.net”;
private const string ServiceName = “TailSpinRegistrationService”;

public IObservable<TaskSummaryResult>
UpdateReceiveNotifications(bool receiveNotifications)
{
this.httpChannel = HttpNotificationChannel.Find(ChannelName);
if (receiveNotifications)
{
if (this.httpChannel != null)
{
this.httpChannel.Close();
this.httpChannel.Dispose();
this.httpChannel = null;
}
this.httpChannel =
new HttpNotificationChannel(ChannelName, ServiceName);

// Bind to the MPNS channel and register the URI with the
// Tailspin Surveys service.
}
if (this.httpChannel != null && this.httpChannel.ChannelUri
!= null)
{
return
this.CallUpdateReceiveNotifications(
false, this.httpChannel.ChannelUri)
.Select(
s =>
{
this.httpChannel.Close();
this.httpChannel.Dispose();
this.httpChannel = null;
return s;
});
}
return Observable.Return(TaskSummaryResult.Success);
}

When the user is enabling notifications, the code in the following example from the UpdateReceiveNotifications method binds to the MPNS channel and registers the URI with the Tailspin Surveys service. It does this by using the Observable.FromEvent method to set up event handlers for the ChannelUriUpdated and ErrorOccurred events on the HttpChannel object before it opens the channel. In the ChannelUriUpdated event handler, it invokes the BindChannel
AndUpdateDeviceUri method. The method also creates a timer task to test for timeouts. Finally, it returns the  bservable TaskSummary Result object from whichever of the three asynchronous tasks completes first.

C#
var channelUriUpdated = from o in
Observable.FromEvent<NotificationChannelUriEventArgs>(
h => this.httpChannel.ChannelUriUpdated += h,
h => this.httpChannel.ChannelUriUpdated -= h)
from summary in this.BindChannelAndUpdateDeviceUriInService()
select summary;
// If this line is not present, the preceding subscription is
// never executed.
this.httpChannel.ChannelUriUpdated += (s, a) => { };
var channelUriUpdateFail = from o in
Observable.FromEvent<NotificationChannelErrorEventArgs>(
h => this.httpChannel.ErrorOccurred += h,
h => this.httpChannel.ErrorOccurred -= h)
select TaskSummaryResult.UnknownError;
this.httpChannel.Open();
// If the notification service does not respond in time, it
// is assumed that the server is unreachable.
var timeout =
Observable
.Interval(TimeSpan.FromSeconds(30))
.Select(i => TaskSummaryResult.UnreachableServer);
return channelUriUpdated.Amb(channelUriUpdateFail).Amb(timeout)
.Take(1);

Note: The Take(1) method does not force execution of the observables; it simply makes sure that the code executes only once.

The following code example shows the BindChannelAndUpdate DeviceUriInService method in the  RegistrationServiceClient class that configures the phone to respond to toast and tile notifications and then calls the CallUpdateReceiveNotifications method to register the unique URI with the Tailspin Surveys web service.

C#
private IObservable<TaskSummaryResult>
BindChannelAndUpdateDeviceUriInService()
{
this.httpChannel = HttpNotificationChannel.Find(ChannelName);
if (!this.httpChannel.IsShellToastBound)
{
this.httpChannel.BindToShellToast();
}
if (!this.httpChannel.IsShellTileBound)
{
this.httpChannel.BindToShellTile();
}
return this.CallUpdateReceiveNotifications(
true, this.httpChannel.ChannelUri);
}

The following code example shows how the CallUpdateReceive Notifications method in the RegistrationServiceClient class registers the clients unique URI with the Tailspin Surveys web service by asynchronously invoking a web method and passing it a DeviceDto object that contains the phone’s unique URI.

C#
private readonly Uri serviceUri;

private IObservable<TaskSummaryResult>
CallUpdateReceiveNotifications(
bool receiveNotifications, Uri channelUri)
{
var device = new DeviceDto
{
Uri = channelUri.ToString(),
RecieveNotifications = receiveNotifications
};
var uri = new Uri(this.serviceUri, “Notifications”);
return
HttpClient
.RequestTo(uri, this.settingsStore.UserName,
this.settingsStore.Password)
.PostJson(device)
.Select(u => TaskSummaryResult.Success);
}

This method uses the HttpClient and HttpWebRequestExtensions classes to post the data transfer object to the web service. Tailspin developed these classes to simplify sending asynchronous HTTP requests from the mobile client application. The following code example shows the RequestTo method from the HttpClient class that creates an Http
WebRequest object.

C#
public static HttpWebRequest RequestTo(
Uri surveysUri, string userName, string password)
{
var request = (HttpWebRequest)WebRequest.Create(surveysUri);
var authHeader = string.Format(CultureInfo.InvariantCulture,
“user {0}:{1}”, userName, password);
request.Headers[HttpRequestHeader.Authorization] = authHeader;
return request;
}

For an explanation of the authorization header that Tailspin Surveys uses to authenticate the client, see the section,

The following code example shows the PostJson method from the HttpWebRequestExtensions class that uses the Observable object from the Reactive Extensions (Rx) framework to call the web method asynchronously. This code example shows four steps:

  • It uses the FromAsyncPattern method to create an asynchronous function that returns an observable Stream object from the HttpWebRequest object.
  • It uses the SelectMany method to asynchronously attach the payload to the request stream.
  • It then returns the WebResponse object from an asynchronous call to the HttpWebRequest object.
  • The method returns an IObservable<Unit> instance, which is equivalent to a null in Rx, when it has a complete HTTP response message.

 

C#
public static IObservable<Unit> PostJson<T>(
this HttpWebRequest request, T obj)
{
request.Method = “POST”;
request.ContentType = “application/json”;
return
Observable
.FromAsyncPattern<Stream>(
request.BeginGetRequestStream, request.EndGetRequestStream)()
.SelectMany(
requestStream =>
{
using (requestStream)
{
var serializer =
new DataContractJsonSerializer(typeof(T));
serializer.WriteObject(requestStream, obj);
requestStream.Close();
}
return
Observable.FromAsyncPattern<WebResponse>(
request.BeginGetResponse,
request.EndGetResponse)();
},
(requestStream, webResponse) => new Unit());
}

So far, this section has described Tailspin’s implementation of the client portion of the registration process. The next part of this section describes how the Surveys service stores the unique URI that the client obtained from the MPNS in Windows Azure storage whenever a Windows Phone 7 device registers for notifications of new surveys.

The following code example shows the implementation of the registration web service that runs in Windows Azure. You can find this class is in the Tailspin.Surveys.Services project.

C#
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode =
InstanceContextMode.PerCall)]
public class RegistrationService : IRegistrationService
{

private readonly IUserDeviceStore userDeviceStore;

public void Notifications(DeviceDto device)
{
var username = Thread.CurrentPrincipal.Identity.Name;
bool isWellFormedUriString = Uri.IsWellFormedUriString(
device.Uri, UriKind.Absolute);
if (isWellFormedUriString)
{
if (device.RecieveNotifications)
{
this.userDeviceStore.SetUserDevice(username,
new Uri(device.Uri));
}
else
{
this.userDeviceStore.RemoveUserDevice(
new Uri(device.Uri));
}
}
}
}

 

The Notifications method receives a DeviceDto parameter object that includes the unique URI allocated to the phone by the MPNS and a Boolean flag to specify whether the user is subscribing or unsubscribing to notifications. The service saves the details in the DeviceDto object in the device store in Windows Azure storage.

Note: In the sample application, the service does not protect the data as it reads and writes to Windows Azure storage. When you deploy your application to Windows Azure, you should secure your table, binary large object (BLOB), and queue endpoints using SSL.

Although phones can explicitly unsubscribe from notifications, the application also removes devices from the device store if it detects that they are no longer registered with the MPNS when it sends a notification.