Windows Phone 7では、Push通知という手法で、トースト通知を行ったり該当アプリのライブタイルに情報を表示したりできます。
Push通知には、Microsoft Push Notification ServiceというPush通知専用のサーバーを利用します。このサーバーはマイクロソフトがWindows PhoneのPush通知用に無料で公開しているサーバーです。
Push通知を使うための大まかな流れは次のようになります。
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image.png)
この中で自作しなくてはならなのは、「Windows Phoneアプリ」と「Push通知用サービス」の2つになります。
「Push通知用サービス」は、インターネット上のWebアプリやWebサービスである必要はありません。「Pusu通知用サービス」に求められる要件は次の3つです。
- Windows PhoneアプリからURIを受信
- URIを利用者ごとに管理
- Push通知したい利用者のURIに対してメッセージ送信
エンドポイント作成(Windows Phoneアプリ)
Push通知サービスを利用するための最初の作業は、Microsoft.Phone.Notificationクラスを使って、Windows Phoneに内蔵されたWindows Phone Push Agent経由でMicrosoft Push Notification Serviceより専用のURIを入手する事です。
Private WithEvents PushChannel As Microsoft.Phone.Notification.HttpNotificationChannel
Private Sub Get_Button_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs)
PushChannel = Microsoft.Phone.Notification.HttpNotificationChannel.Find(MyApplication.ApplicationTitle)
If PushChannel Is Nothing Then
'チャンネル初期利用
PushChannel = New Microsoft.Phone.Notification.HttpNotificationChannel(MyApplication.ApplicationTitle)
PushChannel.Open()
Else
'チャンネル登録済
Call SetUri(PushChannel.ChannelUri.ToString)
End If
If Not PushChannel.IsShellTileBound Then
PushChannel.BindToShellTile()
End If
If Not PushChannel.IsShellToastBound Then
PushChannel.BindToShellToast()
End If
End Sub
HttpNotificationChannel.FindメソッドでWindows Phone内部に保存されている通知用URIを検索します。
もし、通知用URIが保存されていなかったならば、HttpNotificationChannelをOpenしてMicrosoft Push Notification Serviceへ専用URIの習得Requestを送信します。
Windows Phoneでの通信は非同期通信となるので、ChannelUriUpdatedイベントまたはErrorOccurredイベントを待ち合わせます。
Private Sub PushChannel_ChannelUriUpdated(sender As Object,
e As Microsoft.Phone.Notification.NotificationChannelUriEventArgs) _
Handles PushChannel.ChannelUriUpdated
Dispatcher.BeginInvoke(Sub()
Call SetUri(e.ChannelUri.ToString)
End Sub)
End Sub
Private Sub PushChannel_ErrorOccurred(sender As Object,
e As Microsoft.Phone.Notification.NotificationChannelErrorEventArgs) _
Handles PushChannel.ErrorOccurred
Dispatcher.BeginInvoke(Sub()
MessageBox.Show(e.Message, "Get Channel Uri", MessageBoxButton.OK)
End Sub)
End Sub
Private Sub SetUri(ByVal uri As String)
Me.Uri_TextBox.Text = uri
Me.Get_Button.IsEnabled = False
Me.Set_Button.IsEnabled = True
End Sub
PushChannelのイベントが発生したらDispatcher.BeginInvokeを使ってMicrosoft Push Notification Serviceから戻ってきたURI(チャンネルURI)を取得します。このとき、Windows Phone Push Agentにも自動的にチャンネル名と紐づいてURIが自動保存されます。
URI送信(Windows Phoneアプリ)
Push通知を行うためにはMicrosoft Push Notification Serviceから返却されたチャンネルURIが必要です。
そのため実際にPush通知を行うプログラムにチャンネルURIを渡さなければなりません。
今回は、ASP.NET MVC 3で作成したWebアプリからPush通知を行う予定ですので、Windows PhoneからWebアプリにチャンネルURIを送信します。
送信方法は色々ありますが、今回は次のようなコードで送信します。
Private Sub Set_Button_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs)
Dim param As String = "userID=" & System.Net.HttpUtility.UrlEncode(Me.UserID_TextBox.Text.Trim) &
"&password=" & "zzzz" &
"&channelUri=" & System.Net.HttpUtility.UrlEncode(Me.Uri_TextBox.Text.Trim)
Dim uri As String = Me.Server_TextBox.Text & "/Channel/SetChannelUri"
WebClient = New WebClient
WebClient.DownloadStringAsync(New Uri(uri & "?" & param))
End Sub
URI受信(Push通知用サービス)
Windows Phoneアプリからファイル送信で呼び出されるASP.NET MVC 3のコントローラーは次のようなコードになります。
Public Class ChannelController
Inherits System.Web.Mvc.Controller
Function SetChannelUri(ByVal userID As String,
ByVal password As String,
ByVal channelUri As String) As JsonResult
Return Json((New ChannelModel).SetChannelUri(userID, password, channelUri), JsonRequestBehavior.AllowGet)
End Function
End Class
このコントローラーから呼び出されるChannelModelモデルは次のようなコードになっていて、ここでSQL ServerにユーザID(と場合によっては暗号化したパスワード)とチャンネルURIを保存します。
Public Class ChannelModel
Public Function SetChannelUri(ByVal userID As String,
ByVal password As String,
ByVal channelUri As String) _
As TChannelUri
Dim returnValue As TChannelUri
Try
'ここにユーザ認証及びユーザごとのUri保存ロジックを記述
Call SetRecords(userID, password, channelUri)
returnValue = (New TChannelUri(userID,
password,
channelUri,
True,
"OK"))
Catch ex As Exception
returnValue = (New TChannelUri(userID,
password,
channelUri,
False,
ex.Message))
End Try
Return returnValue
End Function
Private Function SetRecords(ByVal userID As String,
ByVal password As String,
ByVal channelUri As String) As Boolean
Dim isOK As Boolean = False
Using _cn As New SqlClient.SqlConnection
Dim isExists As Boolean = False
_cn.ConnectionString = String.Format(GetEnviroment.GetSettings("Connection"),
GetEnviroment.GetSettings("UserID"),
GetEnviroment.GetSettings("Password"))
_cn.Open()
Using _cmd As New SqlClient.SqlCommand
Dim _dr As SqlClient.SqlDataReader
_cmd.Connection = _cn
_cmd.CommandText = "SELECT * FROM Channel WHERE userID=@userID"
_cmd.Parameters.Add(New SqlClient.SqlParameter("@userID", userID))
_dr = _cmd.ExecuteReader
isExists = _dr.Read
_dr.Close()
End Using
Using _cmd As New SqlClient.SqlCommand
_cmd.Connection = _cn
_cmd.Parameters.Add(New SqlClient.SqlParameter("@userID", userID))
_cmd.Parameters.Add(New SqlClient.SqlParameter("@channelUri", channelUri))
If isExists Then
_cmd.CommandText = "UPDATE Channel SET channelUri=@channelUri WHERE userID=@userID"
Else
_cmd.CommandText = "INSERT INTO Channel (userID,channelUri) VALUES (@userID,@channelUri)"
End If
_cmd.ExecuteNonQuery()
End Using
End Using
Return isOK
End Function
End Class
メッセージ送信(Push通知用サービスよりタイル通知を依頼)
それでは登録されている端末に一斉にタイル通知を行うコードを書いてみましょう。記述先はASP.NET MVC 3側になりますが、http POSTができればWebアプリである必要はありません。
注意点は、Push通知は1台1台行う必要がある点です。例えば100人にPush通知するのであれば、Microsoft Push Notification Serviceに対して100回POSTが必要です。できれば常時接続環境、もしくは、インターネット上にホスティングされているサーバーで稼働させているWebアプリから送信するのが良いでしょう。
キャンペーン告知のような不定期に大勢に一斉通知の場合、お勧めは、Windows Azureを活用する事です。キャンペーン告知のPush通知時だけURI受信用のインスタンスとは別にキャンペーン告知用インスタンスを複数立ち上げて通知先をグループ化してパラレルでPush通知処理をすれば短時間で処理が完了し、それぞれの端末にPush通知が届く時間幅が小さくできます。
通知が終わったらキャンペーン告知用インスタンスはインスタンス数を1にするか削除してしまえば費用も抑えられます。
Private Function SendNotification(ByVal channelUri As String,
ByVal counter As Integer,
ByVal backContent As String,
ByVal title As String) As Boolean
Dim isOK As Boolean = False
Dim req As Net.HttpWebRequest = CType(Net.WebRequest.Create(channelUri), Net.HttpWebRequest)
Dim xmldata As XDocument = <?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
<wp:Tile>
<wp:Count><%= counter %></wp:Count>
<wp:Title><%= title %></wp:Title>
<wp:BackTitle>Push Notification</wp:BackTitle>
<wp:BackContent><%= backContent %></wp:BackContent>
</wp:Tile>
</wp:Notification>
Dim notificationMessage As Byte() = Encoding.UTF8.GetBytes(XElement.Parse(xmldata.ToString).ToString)
req.Method = "POST"
req.Headers.Add("X-MessageID", Guid.NewGuid.ToString)
req.ContentLength = notificationMessage.Length
'タイル通知
req.ContentType = "text/xml"
req.Headers.Add("X-WindowsPhone-Target", "token")
req.Headers.Add("X-NotificationClass", "1")
Using reqStream As IO.Stream = req.GetRequestStream
reqStream.Write(notificationMessage, 0, notificationMessage.Length)
reqStream.Close()
End Using
Return isOK
End Function
Push通知依頼の内容はXML文中にしてします指定できる項目としては次の6つがあります。
- Title
- BackGroundImage
- Count
- BackTitle
- BackBackGroundImage
- BackContent
今回はこの中から、Count、Title、BackTitle、BackContentを使っています。
Push通知(Windows Phone)
URIをPush通知用サービスに登録したらスタートボタンでスタートスクリーンに戻ります。
次にアプリケーションリストに移動したらアプリケーションアイコンをタッチアンドホールドして「スタート画面に追加」を選んでアプリケーションをスタートスクリーンに配置します。
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_3.png)
通知
それではPush通知サービスからMicrosoft Push Notificatin Serviceにメッセージを送ってみましょう。
ほぼタイムラグなくライブタイルにカウンタとタイトルが反映されます。
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_4.png)
タイル通知
カウンタはバックカラーなどを考慮した配色になっていますが、タイトルはあまり意識していないようでタイルの背景画像の配色によってはまったく読めません。メトロデザイン的にはきっとタイルデザインはもっとシンプルで単色なものなのかもしれませんね。
タイル全体に画像で情報を表示するようなPush通知を作りたい場合、今回のサンプルでは取り扱っていませんが、173 x 173の大きさの画像をPush通知でタイルの背景画像に指定するとよいでしょう。Windows Phoneアプリのローカルリソース(XAP内のファイル)以外にも80KBまでであればPush通知サービス上の画像を指定する事も出来ます。
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_5.png)
ライブタイルが裏返るとBackContentが表示されます。
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_6.png)
タイル通知のカウンタ削除
カウンタの値を0にするとタイル上のカウンタ表示が消えます
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_7.png)
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_8.png)
メッセージ送信(Push通知用サービスよりトースト通知を依頼)
Push通知のもう1つの方法としてはトースト通知があります。
トースト通知を行うためのMicrosoft Push Notification Serviceに送信するXMLメッセージは次のようなコードになります。
Private Function SendToastNotification(ByVal channelUri As String,
ByVal counter As Integer,
ByVal backContent As String,
ByVal title As String) As Boolean
Dim isOK As Boolean = False
Dim req As Net.HttpWebRequest = CType(Net.WebRequest.Create(channelUri), Net.HttpWebRequest)
Dim xmldata As XDocument = <?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
<wp:Toast>
<wp:Text1><%= title %></wp:Text1>
<wp:Text2><%= backContent %></wp:Text2>
<wp:Param>/SubPage.xaml</wp:Param>
</wp:Toast>
</wp:Notification>
Dim notificationMessage As Byte() = Encoding.UTF8.GetBytes(XElement.Parse(xmldata.ToString).ToString)
req.Method = "POST"
req.Headers.Add("X-MessageID", Guid.NewGuid.ToString)
req.ContentLength = notificationMessage.Length
'トースト通知
req.ContentType = "text/xml"
req.Headers.Add("X-WindowsPhone-Target", "toast")
req.Headers.Add("X-NotificationClass", "2")
Using reqStream As IO.Stream = req.GetRequestStream
reqStream.Write(notificationMessage, 0, notificationMessage.Length)
reqStream.Close()
End Using
Return isOK
End Function
先ほどのWebアプリを少し改造して「トースト通知」をサポートしてみました。
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_9.png)
「トースト通知」をクリックするとメッセージが送信されてWindows Phone上部のところにパンが焼き上がったときのようにトースト通知が飛び出してきます。そしてトースト通知をタップすれば、アプリが起動されてParamに指定したページが自動的に表示されます。Paramを省略された場合はMainPage.xamlが表示されます。
![image image](http://hatsune.wankuma.com/other/WLW/2011/Microsoft-Push-Notification-Service_63DA/image_10.png)
「トースト通知」を依頼したのでタイル通知の方には変化がありません。
タイルの初期化
最後にアプリが起動されたときにタイルの表示を元(タイル通知で変更された内容をクリアする)に戻したいと思います。
Windows PhoneアプリのLoadedイベントプロシージャに次のようなコードを記述します。
Private Sub Me_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
'タイルを初期化
If Microsoft.Phone.Shell.ShellTile.ActiveTiles.Count > 0 Then
Dim tile As New StandardTileData
tile.Title = MyApplication.ApplicationTitle
tile.BackgroundImage = New Uri("/Background.png", UriKind.Relative)
Microsoft.Phone.Shell.ShellTile.ActiveTiles(0).Update(tile)
End If
End Sub
Nyapplication.ApplicationTitleはアセンブリのタイトルを取得してくるように作成した関数です。また、背景イメージもBackground.pngを指定して初期設定イメージにしています。