MSC2011 D3-303 “Windows Phone/iOS/Android から Windows Azure を利用する” session follow up - Part 3 : スクラッチでのソリューション開発 - 3 Push Notification
皆様、こんにちは!次は、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 … ” に変わるまで待ちます。
注意 : ユーザーが、Push Notificationsを有効にした場合、このアプリケーションは、Microsoft Push Notification Service (MPNS) に登録されます。そして、このための Sample Notification Service が、この Toolkit に含まれています。登録した後、このアプリケーションは、順次、Queue に格納されたメッセージを、可能である限り、Windows Phone デバイスのためにダウンロードします。
Push Notification サブスクライブの仕組みは、この図のとおりです。
(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 に格納されており、こんな感じに見えます。
※ MPNSについてさらに知りたい場合には、 Understanding Microsoft Push Notifications for Windows Phones と Understanding 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
ログインすると、今度は今までになかったメニューが表示されています。メニューとしては、(それぞれの権限管理等を行う)Tables、Queues、Users、に加えて、 Micorosoft Push Notifications が追加されているはずです。
注意 : ここでも、Mobile Cloud Application Services Web サイトに表示されるメニューは、アプリケーション作成時に、Wizard 内で選択したオプションにより、若干異なる場合があり得ます。たとえば、SQL Azure データベースを、唯一のデータプロバイダーとして選択した場合、当然ながら、Tables や、Queues メニューは表示されません。.
Microsoft Push Notifications メニューをクリックして、作成したユーザーに向けて、テキストボックス内に、(例えば) “raw message” とタイプして、 Send Raw リンクをクリックします。うまく行けば、Success というメッセージが表示されるはずです。
そこで、Windows Phone Emulatorに戻ると、Messages リストに表示されている、Raw Noftificaion メッセージを確認できます。
次に、テキストボックス内に、(例えば) “tile message” とタイプして、Send Tile リンクをクリックします。 今度はエラーメッセージが表示され、Notification がWindows Phone デバイスで受信できなかった旨の表示がされます。
このように、Tile(Toastも)メッセージを受信できるようにするには、まずはこのアプリケーションを、Windows Phone Startメニューに、ピン留めしておく必要があります。
そこで、Windows Phone Emulator にある Windows button () をクリックし、Start メニューに移動します。Start メニューの中で、右の矢をクリックして() 、すべてのアプリケーションリストを表示します。アプリケーションリスの中で、WA Toolkit WP アイコンを選択し、数秒その状態をホールドします。そうすると、コンテキストメニューが出てきますので、その中から、pin to start をクリックします。これにより、Start メニューにリダイレクトされ、WA Toolkit WP アプリケーションアイコンが、Start メニューに表示されます。
再度、Web ブラウザーに戻り、Send Tile リンクをクリックして、'tile message' Notification を再度送信します。今度は Success メッセージを確認できるでしょう。
今度は、テキストボックスに、(例えば)“toast message”と入力して、Send Toast リンクをクリックします。こちらも Success メッセージを確認できるでしょう。
Windows Phone Emulator に戻ります。WAT Windows Phone アイコンに表示されているTile Notification の数字 (ここでは1)と、上部に ある Toast Message が確認できます。いずれかをクリックすると、WAT Windows Phone アプリケーションが開き、Notifications ページに遷移します。ここで表示される Messages リストの中に、両方のメッセージ('tile message' と 'toast message') があるのが確認できるでしょう。
なお、ログインページにリダイレクトされないのは、認証トークンを、最初にログインした時に受け取っており、そのトークンは、標準で、当該デバイス(エミュレーター)の Isolated Storage に格納されているためです。
さて、このメッセージ群も、Azure Storage Explorer で見てみましょう。これは Queue に格納されており、例えば、このように見えます。
以上で、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 編に入ります。
鈴木 章太郎