皆様、こんにちは!次は、Push Notification の箇所です。簡単に行きましょう。

Sending Microsoft Push Notifications

新規 Windows Phone Cloud Application の作成の箇所で、Microsoft Push Notificationsのサポートにチェックを入れた場合、Notificationのメッセージを、Windows Phone デバイスに送信することができます。そのためのステップは以下の通りです。

このWPアプリケーションは、Pivotアプリケーションとして作られています。そこで、ユーザー認証が済んだら、次に Notification Pivotアイテムが、アプリケーションに現れます。ここではまだ、Push Notification が有効になっていないことに気づきます。したがって、Connection Status は、未だ Disconnected のままです。

そこで、このチェックボックスで、Enable Push Notifications にチェックを入れ、画面下部に出rてくるメッセージが、 ”Notification updates were received at … ” に変わるまで待ちます。

clip_image0054_thumb2_thumb

注意 : ユーザーが、Push Notificationsを有効にした場合、このアプリケーションは、Microsoft Push Notification Service (MPNS) に登録されます。そして、このための Sample Notification Service が、この Toolkit に含まれています。登録した後、このアプリケーションは、順次、Queue に格納されたメッセージを、可能である限り、Windows Phone デバイスのためにダウンロードします。

Push Notification サブスクライブの仕組みは、この図のとおりです。

image

(1)Phone が単一の Channel をオープンします。

(2)Phone が Web Role に URL を送信します。

(3)Web Role が、当該 URL を使って、Notifications を Push します。

(4)Microsoft Push Notification Service が Phone に通知します。

 

ちなみに、下記は、Azure Storage Explorer で見た、あるPushUserEndPointです。Table に格納されており、こんな感じに見えます。

image

※ MPNSについてさらに知りたい場合には、 Understanding Microsoft Push Notifications for Windows PhonesUnderstanding How Microsoft Push Notification Works – Part 2 、両記事をぜひご一読ください。

簡単に特徴と種類をまとめると下記のとおりです。今後、MSDNでも記述が充実してくるはずなので、ご期待ください。

Push Notificationsの特徴

・Phone と Microsoft Push Notification Service との間の単一のコネクション
・帯域の節約とバッテリ消費の逓減
・配信される保証はない

・有効期間が決まっている

Push Notifications の種類
・Raw – 単一メッセージを単一アプリケーションに送信
・トースト - 単一メッセージをユーザーに送信 (デバイスID)
・タイル – イメージ、タイトル、カウントの更新

それでは次に、Web ブラウザーに戻って、Mobile Cloud Application Services Web サイトを表示します。Log On リンクをクリックし、前の記事と同じように、このアプリケーションにログインします。この Web アプリケーションにログインするには下記のクレデンシャルを使用します:

◦ User Name: admin

◦ Password: clip_image0066_thumb_thumb (with a zero)

ログインすると、今度は今までになかったメニューが表示されています。メニューとしては、(それぞれの権限管理等を行う)Tables、Queues、Users、に加えて、 Micorosoft Push Notifications が追加されているはずです。

注意 : ここでも、Mobile Cloud Application Services Web サイトに表示されるメニューは、アプリケーション作成時に、Wizard 内で選択したオプションにより、若干異なる場合があり得ます。たとえば、SQL Azure データベースを、唯一のデータプロバイダーとして選択した場合、当然ながら、Tables や、Queues メニューは表示されません。.

clip_image0074_thumb2_thumb

Microsoft Push Notifications メニューをクリックして、作成したユーザーに向けて、テキストボックス内に、(例えば) “raw message” とタイプして、 Send Raw リンクをクリックします。うまく行けば、Success というメッセージが表示されるはずです。

clip_image0086_thumb2_thumb

そこで、Windows Phone Emulatorに戻ると、Messages リストに表示されている、Raw Noftificaion メッセージを確認できます。

clip_image0104_thumb2_thumb

次に、テキストボックス内に、(例えば) “tile message” とタイプして、Send Tile リンクをクリックします。 今度はエラーメッセージが表示され、Notification がWindows Phone デバイスで受信できなかった旨の表示がされます。

clip_image0096_thumb2_thumb

このように、Tile(Toastも)メッセージを受信できるようにするには、まずはこのアプリケーションを、Windows Phone Startメニューに、ピン留めしておく必要があります。

そこで、Windows Phone Emulator にある Windows button (clip_image0116_thumb_thumb) をクリックし、Start メニューに移動します。Start メニューの中で、右の矢をクリックして(clip_image012_thumb_thumb) 、すべてのアプリケーションリストを表示します。アプリケーションリスの中で、WA Toolkit WP アイコンを選択し、数秒その状態をホールドします。そうすると、コンテキストメニューが出てきますので、その中から、pin to start をクリックします。これにより、Start メニューにリダイレクトされ、WA Toolkit WP アプリケーションアイコンが、Start メニューに表示されます。

clip_image0134_thumb2_thumb

再度、Web ブラウザーに戻り、Send Tile リンクをクリックして、'tile message' Notification を再度送信します。今度は Success メッセージを確認できるでしょう。

clip_image014_thumb2_thumb

今度は、テキストボックスに、(例えば)“toast message”と入力して、Send Toast リンクをクリックします。こちらも Success メッセージを確認できるでしょう。

clip_image0154_thumb2_thumb

Windows Phone Emulator に戻ります。WAT Windows Phone アイコンに表示されているTile Notification の数字 (ここでは1)と、上部に ある Toast Message が確認できます。いずれかをクリックすると、WAT Windows Phone アプリケーションが開き、Notifications ページに遷移します。ここで表示される Messages リストの中に、両方のメッセージ('tile message' と 'toast message') があるのが確認できるでしょう。

clip_image0164_thumb2_thumb

なお、ログインページにリダイレクトされないのは、認証トークンを、最初にログインした時に受け取っており、そのトークンは、標準で、当該デバイス(エミュレーター)の Isolated Storage に格納されているためです。

さて、このメッセージ群も、Azure Storage Explorer で見てみましょう。これは Queue に格納されており、例えば、このように見えます。

image image

以上で、Push Notification の部分のデモの解説は終了です。このあたり、サンプル実装が非常によくできていますので、ぜひ内容をご覧ください。

次のソースは、自動生成されている SamplePushUserRegistrationResponse.cs です。Connect メソッドで、Table に、Push の Channel URIを書き込んでいます。上記のPush Notification サブスクライブの仕組みを再度見ながら、流れを追ってみてください。

   1: namespace WPCloudApp5.Phone.Push
   2: {
   3:     using System;
   4:     using System.Globalization;
   5:     using System.IO;
   6:     using System.Net;
   7:     using System.Net.Browser;
   8:     using System.Runtime.Serialization;
   9:     using System.Text;
  10:     using Microsoft.Samples.WindowsPhoneCloud.StorageClient;
  11:     using Microsoft.Samples.WindowsPhoneCloud.StorageClient.Credentials;
  12:     using WPCloudApp5.Phone.Models;
  13:  
  14:     public class SamplePushUserRegistrationClient : ISamplePushUserRegistrationClient
  15:     {
  16:         private const string RegisterNotificationOperation = "/register";
  17:         private const string UnregisterNotificationOperation = "/unregister";
  18:         private const string GetUpdatesNotificationOperation = "/updates";
  19:  
  20:         private readonly Uri serviceEndpoint;
  21:         private readonly IStorageCredentials storageCredentials;
  22:         private readonly string applicationId;
  23:  
  24:         public SamplePushUserRegistrationClient(Uri serviceEndpoint, IStorageCredentials storageCredentials, string applicationId)
  25:         {
  26:             this.serviceEndpoint = serviceEndpoint;
  27:             this.storageCredentials = storageCredentials;
  28:             this.applicationId = applicationId;
  29:         }
  30:  
  31:         public void Connect(Action<SamplePushUserRegistrationResponse<string>> callback)
  32:         {
  33:             var registerOperationUriBuilder = new UriBuilder(this.serviceEndpoint);
  34:             registerOperationUriBuilder.Path += RegisterNotificationOperation;
  35:  
  36:             PushContext.Current.Connect(c => ExecutePostServiceOperation<string>(registerOperationUriBuilder.Uri, this.storageCredentials, c.ChannelUri, this.applicationId, callback));
  37:         }
  38:  
  39:         public void Disconnect(Action<SamplePushUserRegistrationResponse<string>> callback)
  40:         {
  41:             if (PushContext.Current.NotificationChannel != null)
  42:             {
  43:                 var channelUri = PushContext.Current.NotificationChannel.ChannelUri;
  44:                 var unregisterOperationUriBuilder = new UriBuilder(this.serviceEndpoint);
  45:  
  46:                 unregisterOperationUriBuilder.Path += UnregisterNotificationOperation;
  47:                 ExecutePostServiceOperation<string>(unregisterOperationUriBuilder.Uri, this.storageCredentials, channelUri, this.applicationId, callback);
  48:             }
  49:             else
  50:             {
  51:                 callback(new SamplePushUserRegistrationResponse<string>(string.Empty, null));
  52:             }
  53:  
  54:             PushContext.Current.Disconnect();
  55:         }
  56:  
  57:         public void GetUpdates(Action<SamplePushUserRegistrationResponse<string[]>> callback)
  58:         {
  59:             if (PushContext.Current.NotificationChannel != null)
  60:             {
  61:                 var channelUri = PushContext.Current.NotificationChannel.ChannelUri;
  62:                 var getUpdatesOperationUriBuilder = new UriBuilder(this.serviceEndpoint);
  63:                 getUpdatesOperationUriBuilder.Path += GetUpdatesNotificationOperation;
  64:  
  65:                 ExecuteGetServiceOperation<string[]>(getUpdatesOperationUriBuilder.Uri, this.storageCredentials, channelUri, this.applicationId, callback);
  66:             }
  67:         }
  68:  
  69:         private static void ExecuteGetServiceOperation<T>(Uri serviceOperationUri, IStorageCredentials storageCredentials, Uri channelUri, string applicationId, Action<SamplePushUserRegistrationResponse<T>> callback)
  70:         {
  71:             string deviceId = App.GetDeviceId();
  72:             var getOperationUri = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}?applicationId={1}&deviceId={2}", serviceOperationUri, applicationId, deviceId));
  73:  
  74:             var request = WebRequestCreator.ClientHttp.Create(getOperationUri);
  75:             request.Method = "GET";
  76:  
  77:             try
  78:             {
  79:                 storageCredentials.SignRequest(request, -1);
  80:                 request.BeginGetResponse(
  81:                     ar =>
  82:                     {
  83:                         var result = default(T);
  84:                         try
  85:                         {
  86:                             var response = request.EndGetResponse(ar);
  87:                             var serializer = new DataContractSerializer(typeof(T));
  88:  
  89:                             result = (T)serializer.ReadObject(response.GetResponseStream());
  90:                             callback(new SamplePushUserRegistrationResponse<T>(result, null));
  91:                         }
  92:                         catch (Exception exception)
  93:                         {
  94:                             callback(new SamplePushUserRegistrationResponse<T>(default(T), StorageClientExceptionParser.ParseStringWebException(exception as WebException) ?? exception));
  95:                         }
  96:                     },
  97:                     null);
  98:             }
  99:             catch (Exception exception)
 100:             {
 101:                 callback(new SamplePushUserRegistrationResponse<T>(default(T), exception));
 102:             }
 103:         }
 104:  
 105:         private static void ExecutePostServiceOperation<T>(Uri serviceOperationUri, IStorageCredentials storageCredentials, Uri channelUri, string applicationId, Action<SamplePushUserRegistrationResponse<T>> callback)
 106:         {
 107:             var request = WebRequestCreator.ClientHttp.Create(serviceOperationUri);
 108:             request.Method = "POST";
 109:             request.ContentType = "text/xml";
 110:  
 111:             var postData = string.Empty;
 112:             using (var stream = new MemoryStream())
 113:             {
 114:                 var serializer = new DataContractSerializer(typeof(PushUserServiceRequest));
 115:                 string deviceId = App.GetDeviceId();
 116:                 var newPushUserRegistration = new PushUserServiceRequest { ApplicationId = applicationId, DeviceId = deviceId, ChannelUri = channelUri };
 117:                 serializer.WriteObject(stream, newPushUserRegistration);
 118:  
 119:                 byte[] contextAsByteArray = stream.ToArray();
 120:                 postData = Encoding.UTF8.GetString(contextAsByteArray, 0, contextAsByteArray.Length);
 121:             }
 122:  
 123:             try
 124:             {
 125:                 storageCredentials.SignRequest(request, postData.Length);
 126:                 request.BeginGetRequestStream(
 127:                     ar =>
 128:                     {
 129:                         var postStream = request.EndGetRequestStream(ar);
 130:                         var byteArray = Encoding.UTF8.GetBytes(postData);
 131:  
 132:                         postStream.Write(byteArray, 0, postData.Length);
 133:                         postStream.Close();
 134:  
 135:                         request.BeginGetResponse(
 136:                             asyncResult =>
 137:                             {
 138:                                 var result = default(T);
 139:                                 try
 140:                                 {
 141:                                     var response = request.EndGetResponse(asyncResult);
 142:                                     var serializer = new DataContractSerializer(typeof(T));
 143:  
 144:                                     result = (T)serializer.ReadObject(response.GetResponseStream());
 145:                                     callback(new SamplePushUserRegistrationResponse<T>(result, null));
 146:                                 }
 147:                                 catch (Exception exception)
 148:                                 {
 149:                                     callback(new SamplePushUserRegistrationResponse<T>(default(T), StorageClientExceptionParser.ParseStringWebException(exception as WebException) ?? exception));
 150:                                 }
 151:                             },
 152:                         null);
 153:                     },
 154:                 request);
 155:             }
 156:             catch (Exception exception)
 157:             {
 158:                 callback(new SamplePushUserRegistrationResponse<T>(default(T), exception));
 159:             }
 160:         }
 161:     }
 162: }

以上です。次のエントリは、Table/Blob/Queue 及び SQL Azure の操作、と、ソースコードの紹介(おもに、ListBlobsPageViewModel()、UploadPhotoPageViewModel()等)です。

これが終わり次第、iOS + Windows Azure 編に入ります。

鈴木 章太郎