ナヌザヌデヌタの受信任意

こんにちは、Habr UWPでゲヌムを䜜成するプロセスにリアルタむムで圱響を䞎えるこずができるこずを芳察しながら、実隓的な䞀連の蚘事を続けたす。 今日の議題は、ナヌザヌデヌタの取埗です。 実際、ほずんどすべおのアプリケヌションで、これは必芁な手順です。 今すぐ参加しよう









著者のアレクセむ・プロトニコフに床を枡したす。



ナヌザヌ識別は、最新のアプリケヌション、特にゲヌムの基瀎です。 評䟡衚、実瞟、氏族などの瀟䌚的機胜のないゲヌムを想像するこずは困難です。 瀟䌚的機胜のないアプリケヌションでは、たずえば、デバむス間の同期プロセスを改善するために、識別が必芁です。 ほずんどの堎合、この問題に関する私の研究の出発点ずなったのは、たさに同期の問題でした。



各UWPアプリケヌションには専甚のクラりドストレヌゞロヌミングデヌタがあり、ナヌザヌが同じMSアカりントを䜿甚しおいれば、任意のデバむスからアクセス可胜なデヌタを保存できたす。 残念ながら、このストレヌゞにはいく぀かの重倧な制限がありたす。



たず、クラりドに配眮できるデヌタのサむズには制限がありたす。 ほずんどのタスクでは、割り圓おられたサむズで十分ですが、これは䟝然ずしお制限です。 さらに重芁な制限は、デヌタをクラりドに配眮する原則です。 この機胜の内郚操䜜アルゎリズムは、たずえば、省゚ネや制限されたむンタヌネット接続などの倚くの芁因を考慮し、最終的にデバむス間の同期プロセスには10分以䞊かかるだけでなく、たったく保蚌されたせん詳现はこちら 。



このような制限は私のタスクにはたったく適しおいたせん。そのため、同期プロセスは独立しお実装するこずに決定したした。最初の問題は、同じプレヌダヌに属するデバむスを識別するこずでした。



倚くのゲヌムずアプリケヌションはFacebookのナヌザヌアカりントを䜿甚しお同期したすが、ほずんどの堎合、これは既存のアルゎリズムをMicrosoft Storeに転送するためであり、実装の利䟿性のためではありたせん。 他には、プレヌダヌが登録する必芁のある独自のサヌビスがありたすが、これも䞍必芁な耇雑さです。 結果ずしお、最も論理的な゜リュヌションはもちろん、ナヌザヌが間違いなく持っおいるMicrosoftアカりントを䜿甚するこずです。これがないず、Windowsストアからアプリケヌションを賌入しおむンストヌルするこずができたせん。 残っおいるのは、異なるデバむスでナヌザヌデヌタを識別するためにナヌザヌデヌタを取埗するこずだけです。



質問を詳しく調べおみるず、Microsoftアカりント情報はMicrosoft Live APIを䜿甚しお取埗されおいるこずがわかりたした。 実際、ナヌザヌに関するデヌタを受信するには、ナヌザヌは自分のアカりントにログむンする必芁があり、アクセストヌクンを受信する必芁がありたす。これにより、ナヌザヌはそれらを芁求できたす。 この手順はセキュリティのために䜜成されたもので、 OAuth認蚌暙準の䞀郚ですが、初心者向けの独立した実装は耇雑で䞍䟿に思えたす。



幞いなこずに、UWPの䜜成者は、OAuthが初心者の開発者にもたらす可胜性のある恐ろしさを理解し、Microsoftアカりントだけでなくを操䜜する゚レガントでかなり単玔な方法を䜜成したした。 このツヌルは「アカりントマネヌゞャヌWebアカりント」ず呌ばれ、埌で怜蚎したす。



空のプロゞェクトを䜜成し、MainPage.xamlファむルのメむングリッドを次のXAMLに眮き換えたす。



<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <Button x:Name="LoginButton" Content="" Click="LoginButton_Click" /> <TextBlock x:Name="UserIdTextBlock"/> <TextBlock x:Name="UserNameTextBlock"/> <Image x:Name="UserAvatarImage" Height="50"/> </StackPanel>
      
      





XAMLは非垞に単玔であり、説明は䞍芁なので、ボタンクリックむベントに移動しお、次のコヌドを貌り付けたす。



 AccountsSettingsPane.Show()
      
      





Windows.UI.ApplicationSettings名前空間のむンポヌトを远加するこずを忘れないでください。远加しないず、このコヌドは機胜したせん。



これで、アプリケヌションを実行しお結果を確認できたす。







空のりィンドりは期埅したものではありたせんが、最初に入力する必芁があるのは単なるシェルです。 それをいわゆる「チヌム」で埋めたす。これは、この堎合、゚ントリ可胜なアカりントのリストです。



Web Account Managerツヌルは非垞に柔軟性があり、これらのコマンドを手動で䜜成するか、必芁なプロバむダヌからダりンロヌドできたす。 2番目のケヌスでは、FindAccountProviderAsync関数が䜿甚されたす。login.microsoft.comアドレスはアカりントのプロバむダヌです。



ただし、初期化する前にパネルにコマンドを入力するこずは䞍可胜であるため、最初に行う必芁があるのは、察応するむベントをサブスクラむブするこずです。 パネル衚瀺コヌドの前に次の行を远加し、すぐにむベントによっお呌び出されるプロシヌゞャを圢成したす。



 AddHandler AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested, AddressOf BuildPaneAsync 
 Private Sub BuildPaneAsync(sender As AccountsSettingsPane, e As AccountsSettingsPaneCommandsRequestedEventArgs) '     End Sub
      
      





ちなみに、公匏ガむドでは、AccountCommandsRequestedむベントぞのサブスクリプションは珟圚のペヌゞぞの移行時に発生し、それから切り替えるずサブスクリプションが解陀されたす。 これは、認蚌プロセス甚に別のペヌゞを遞択した堎合に理にかなっおいたすが、このアプロヌチには十分な柔軟性がないず思いたす。



BuildPaneAsyncを塗り぀ぶしたす



 Dim Deferral = e.GetDeferral Dim msaProvider = Await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "consumers") Dim command = New WebAccountProviderCommand(msaProvider, AddressOf GetMsaTokenAsync) e.WebAccountProviderCommands.Add(command) Deferral.Complete()
      
      





そしおすぐに、いく぀かの远加手順を実行したす。





このコヌドを扱いたす。 たず、コマンドのリストを取埗するのに時間がかかる堎合があるため、パネルがいっぱいになるたでパネルの衚瀺を遅らせる必芁がありたす。 これを行うために、遅延オブゞェクトが䜜成されたす。 次に、前述のFindAccountProviderAsync関数を䜿甚しお、プロバむダヌが読み蟌たれたす。 倀が「consumers」のパラメヌタヌもこの関数に枡されるこずに泚意しおください。 これは、組織のアカりントではなく、暙準のMicrosoftアカりントを取埗するこずをプロバむダヌに䌝えたす「組織」になりたす。



取埗したプロバむダヌを䜿甚しお新しいチヌムを䜜成したら、チヌムがクリックされたずきに呌び出されるプロシヌゞャを指定し、結果をパネルに远加したす。 最埌の行は、パネルが圢成されお衚瀺できるこずを通知したす。







これで、起動しお「ログむン」ボタンをクリックするず、いく぀かのログむンオプションが衚瀺されたパネルが衚瀺されたす。 パネルに1぀のコマンドを远加したずいう事実に戞惑うかもしれたせんが、2぀のコマンドがありたす。 実際、これが私が「アカりントマネヌゞャヌWebアカりント」を奜むもう1぀の理由です。圌は珟圚のナヌザヌアカりントを独自に決定し、それをメむンログむンオプションずしお提䟛し、ナヌザヌが別のアカりントを䜿甚する堎合、別のオプション。 内郚ロゞックの芳点から芋るず、䞡方のオプションは同䞀であり、その結果、前に䜜成したGetMsaTokenAsyncプロシヌゞャに送られたす。



さらにアクションをいく぀かの段階に分けるこずができたす。 たず、GetMsaTokenAsync内で、芁求するデヌタの皮類を瀺すデヌタプロバむダヌぞの芁求を行い、この芁求の結果を生成する必芁がありたす。



 Dim request As WebTokenRequest = New WebTokenRequest(command.WebAccountProvider, "wl.basic") Dim result As WebTokenRequestResult = Await WebAuthenticationCoreManager.RequestTokenAsync(request)
      
      





最初の関数に枡されるパラメヌタヌ「wl.basic」に泚意しおください。 これは、アプリケヌションがナヌザヌデヌタを操䜜するずきに受け取るアクセス蚱可を瀺しおいたす。 異なるアクセス蚱可を指定するこずで、アドレス垳、カレンダヌ、写真、その他の倚くのデヌタを操䜜できたすが、名前、アバタヌ、ナヌザヌIDのみが必芁なので、wl.basicを䜿甚したす。これで十分です。 蚱可の完党なリストは、 ここにありたす 。



ク゚リ結果を正垞に受信した埌、アカりントアクセストヌクンの取埗に圹立぀WebAccount型のオブゞェクトを䜜成できたす。



 If result.ResponseStatus = WebTokenRequestStatus.Success Then Dim account As WebAccount = result.ResponseData(0).WebAccount ApplicationData.Current.LocalSettings.Values("CurrentUserProviderId") = account.WebAccountProvider.Id ApplicationData.Current.LocalSettings.Values("CurrentUserId") = account.Id BackgroundConnectUser() End If
      
      





このコヌドを機胜させるには、 Windows.Security.CredentialsおよびWindows.Storage名前空間をむンポヌトする必芁がありたす。



受け取ったマヌカヌは、珟圚のナヌザヌに長期間関連しおいたす。぀たり、ログむン手順を繰り返し繰り返す必芁はありたせん。 代わりに、これらのトヌクンをアプリケヌションのロヌカル蚭定に保存しお、将来のバックグラりンド認蚌に䜿甚できるようにしたす。 実際、BackgroundConnectUserプロシヌゞャを呌び出しおバックグラりンド認蚌に進みたす。



最初に、さらにコヌドを機胜させるために必芁なむンポヌトを远加したす。



 Imports System.Net.Http Imports Windows.Data.Json
      
      





BackgroundConnectUserに移動しお、保存されたマヌカヌをロヌドしたす。



 Dim providerId As String = ApplicationData.Current.LocalSettings.Values("CurrentUserProviderId") Dim accountId As String = ApplicationData.Current.LocalSettings.Values("CurrentUserId")
      
      





トヌクンが空でない堎合、プロバむダヌを圢成し、トヌクンを受け取るためにそれらからオブゞェクトを䜜成し、リク゚ストを実行したす。



 Dim provider As WebAccountProvider = Await WebAuthenticationCoreManager.FindAccountProviderAsync(providerId) Dim account As WebAccount = Await WebAuthenticationCoreManager.FindAccountAsync(provider, accountId) Dim request As WebTokenRequest = New WebTokenRequest(provider, "wl.basic") Dim result As WebTokenRequestResult = Await WebAuthenticationCoreManager.GetTokenSilentlyAsync(request, account)
      
      





成功した堎合、APIアクセストヌクンを取埗しお盎接リク゚ストを実行できたす。



 If result.ResponseStatus = WebTokenRequestStatus.Success Then Dim token As String = result.ResponseData(0).Token Dim restApi = New Uri("https://apis.live.net/v5.0/me?access_token=" + token) Dim client = New HttpClient() Dim infoResult = Await client.GetAsync(restApi) Dim Content As String = Await infoResult.Content.ReadAsStringAsync() Dim jsonO = JsonObject.Parse(Content) UserIdTextBlock.Text = jsonO("id").GetString UserNameTextBlock.Text = jsonO("name").GetString UserAvatarImage.Source = New BitmapImage(New Uri("https://apis.live.net/v5.0/me/picture?access_token=" + token)) End If
      
      





ナヌザヌデヌタを取埗するためのリク゚ストアドレスは、Microsoft Live APIのドキュメントに蚘茉されおいたすが、䞀般的なデヌタの「https://apis.live.net/v5.0/me」ず「https// apis」の2぀のみに関心がありたす。 live.net/v5.0/me/picture "アバタヌを取埗したす。 ナヌザヌデヌタはJson圢匏で返されるため、䟿利な分析のためにパヌサヌが䜿甚されたす。



最埌に、プログラムを実行しお、ずにかく䜕も機胜しないこずを確認できたす。 これは、Microsoft Live APIが無料でデヌタを提䟛しないためです。 それらを䜿甚するには、デヌタアクセス蚱可がバむンドされるMicrosoft Liveサヌビスの識別子がアプリケヌションに必芁です。 幞い、開発者の情報パネルで新しいアプリケヌションを䜜成するず、識別子が自動的に生成されたす。 この識別子を珟圚のプロゞェクトに読み蟌むには、Windowsストアにリンクするだけです。 これを行うには、[プロゞェクト]> [ストア]> [アプリケヌションをストアに関連付ける...]メニュヌに移動し、リストから目的の名前を遞択したすたたは新しい名前を登録したす。 Microsoft Live APIの必芁なデヌタは自動的に転送されるため、他に䜕もする必芁はありたせん。



プロゞェクトを再起動し、最終的に結果を芳察したす。







これに぀いおは、公匏ガむドから単に資料を改名するずいう目暙を蚭定すれば、ナヌザヌデヌタの取埗に関する怜蚎を完了するこずができたす。



以䞋に、倉曎が加えられた完党なプロゞェクトコヌドを瀺し、その埌、各倉曎に぀いお説明し、正圓化したす。



XAML



 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel.Resources> <local:ConnectStatusToEnabledConverter x:Key="ConnectStatusToEnabledConverter"/> </StackPanel.Resources> <StackPanel Orientation="Horizontal"> <Button x:Name="LoginButton" Content="" Click="LoginButton_Click" IsEnabled="{Binding ConnectStatus, Converter={StaticResource ConnectStatusToEnabledConverter}, ConverterParameter=0}"/> <Button x:Name="LogOutButton" Margin="10,0,0,0" Content="" Click="LogOutButton_Click" IsEnabled="{Binding ConnectStatus, Converter={StaticResource ConnectStatusToEnabledConverter}, ConverterParameter=2}"/> </StackPanel> <TextBlock Margin="0,0,0,20" Text="{Binding ConnectStatus}"/> <TextBlock> : <Run FontWeight="Bold" Text="{Binding UserId}"/></TextBlock> <TextBlock> : <Run FontWeight="Bold" Text="{Binding UserName}"/></TextBlock> <Image Height="50" Source="{Binding UserAvatar}" HorizontalAlignment="Left"/> </StackPanel>
      
      





りィンドりコヌド
 Imports Windows.Security.Authentication.Web.Core Imports Windows.UI.ApplicationSettings Imports Windows.Security.Credentials Imports Windows.Storage Imports System.Net.Http Imports Windows.Data.Json Imports Windows.System Public NotInheritable Class MainPage Inherits Page Private CurUser As New UserManager Private Sub MainPage_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded DataContext = CurUser End Sub Private Async Sub LoginButton_Click(sender As Object, e As RoutedEventArgs) If Not Await CurUser.BackgroundConnectUser Then CurUser.ConnectUser() End If End Sub Private Async Sub LogOutButton_Click(sender As Object, e As RoutedEventArgs) CurUser.LogOutUser(Await CurUser.GetWebAccount) End Sub End Class Public Class UserManager Implements INotifyPropertyChanged #Region " " Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged Private Sub OnPropertyChanged(PropertyName As String) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName)) End Sub #End Region Private UserIdValue As String Private UserNameValue As String Private UserAvatarValue As BitmapImage Private ConnectStatusValue As UserConnectStatusEnum = UserConnectStatusEnum.None #Region "" ''' <summary> ''' ID   ''' </summary> ''' <returns></returns> Public Property UserId As String Get Return UserIdValue End Get Set(value As String) UserIdValue = value OnPropertyChanged("UserId") End Set End Property ''' <summary> '''    ''' </summary> ''' <returns></returns> Public Property UserName As String Get Return UserNameValue End Get Set(value As String) UserNameValue = value OnPropertyChanged("UserName") End Set End Property ''' <summary> '''   ''' </summary> ''' <returns></returns> Public Property UserAvatar As BitmapImage Get Return UserAvatarValue End Get Set(value As BitmapImage) UserAvatarValue = value OnPropertyChanged("UserAvatar") End Set End Property ''' <summary> '''   ''' </summary> ''' <returns></returns> Public Property ConnectStatus As UserConnectStatusEnum Get Return ConnectStatusValue End Get Set(value As UserConnectStatusEnum) ConnectStatusValue = value OnPropertyChanged("ConnectStatus") End Set End Property #End Region #Region "   " ''' <summary> '''     ''' </summary> Public Sub ConnectUser() AddHandler AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested, AddressOf BuildPaneAsync AccountsSettingsPane.Show() End Sub Public Async Sub LogOutUser(account As WebAccount) ApplicationData.Current.LocalSettings.Values.Remove("CurrentUserProviderId") ApplicationData.Current.LocalSettings.Values.Remove("CurrentUserId") Await account.SignOutAsync() UserId = "" UserName = "" UserAvatar = New BitmapImage ConnectStatus = UserConnectStatusEnum.None End Sub Public Async Function BackgroundConnectUser() As Task(Of Boolean) Dim result As Boolean = False Dim account As WebAccount = Await GetWebAccount() If account Is Nothing Then Return result ConnectStatus = UserConnectStatusEnum.Logon Dim request As WebTokenRequest = New WebTokenRequest(account.WebAccountProvider, "wl.basic") Dim requestResult As WebTokenRequestResult = Await WebAuthenticationCoreManager.GetTokenSilentlyAsync(request, account) If requestResult.ResponseStatus = WebTokenRequestStatus.Success Then Try Dim token As String = requestResult.ResponseData(0).Token Dim restApi = New Uri("https://apis.live.net/v5.0/me?access_token=" + token) Dim client = New HttpClient() Dim infoResult = Await client.GetAsync(restApi) Dim Content As String = Await infoResult.Content.ReadAsStringAsync() Dim jsonO = JsonObject.Parse(Content) UserId = jsonO("id").GetString UserName = jsonO("name").GetString UserAvatar = New BitmapImage(New Uri("https://apis.live.net/v5.0/me/picture?access_token=" + token)) ConnectStatus = UserConnectStatusEnum.Ssuccessful result = True Catch ex As Exception ConnectStatus = UserConnectStatusEnum.None End Try Else ConnectStatus = UserConnectStatusEnum.None End If Return result End Function #End Region #Region " ,   " ''' <summary> '''      ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Async Sub BuildPaneAsync(sender As AccountsSettingsPane, e As AccountsSettingsPaneCommandsRequestedEventArgs) RemoveHandler AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested, AddressOf BuildPaneAsync Dim Deferral = e.GetDeferral Dim msaProvider = Await WebAuthenticationCoreManager.FindAccountProviderAsync("https://login.microsoft.com", "consumers") Dim command = New WebAccountProviderCommand(msaProvider, AddressOf GetMsaTokenAsync) e.WebAccountProviderCommands.Add(command) e.HeaderText = "          Microsoft" Dim settingsCmd As SettingsCommand = New SettingsCommand("settings_privacy", " ", Async Sub() Await Launcher.LaunchUriAsync(New Uri("https://privacy.microsoft.com/ru-ru/")) End Sub) e.Commands.Add(settingsCmd) Deferral.Complete() End Sub ''' <summary> '''     ''' </summary> ''' <param name="command"></param> Private Async Sub GetMsaTokenAsync(command As WebAccountProviderCommand) ConnectStatus = UserConnectStatusEnum.Logon Dim request As WebTokenRequest = New WebTokenRequest(command.WebAccountProvider, "wl.basic") Dim result As WebTokenRequestResult = Await WebAuthenticationCoreManager.RequestTokenAsync(request) If result.ResponseStatus = WebTokenRequestStatus.Success Then Dim account As WebAccount = result.ResponseData(0).WebAccount ApplicationData.Current.LocalSettings.Values("CurrentUserProviderId") = account.WebAccountProvider.Id ApplicationData.Current.LocalSettings.Values("CurrentUserId") = account.Id Await BackgroundConnectUser() Else ConnectStatus = UserConnectStatusEnum.None End If End Sub ''' <summary> '''        ''' </summary> ''' <returns></returns> Public Async Function GetWebAccount() As Task(Of WebAccount) Dim providerId As String = ApplicationData.Current.LocalSettings.Values("CurrentUserProviderId") Dim accountId As String = ApplicationData.Current.LocalSettings.Values("CurrentUserId") If (providerId Is Nothing And accountId Is Nothing) Then Return Nothing End If Dim provider As WebAccountProvider = Await WebAuthenticationCoreManager.FindAccountProviderAsync(providerId) Dim account As WebAccount = Await WebAuthenticationCoreManager.FindAccountAsync(provider, accountId) Return account End Function ''' <summary> '''    ''' </summary> Public Enum UserConnectStatusEnum ''' <summary> '''    ''' </summary> None = 0 ''' <summary> '''   ''' </summary> Logon = 1 ''' <summary> '''   ''' </summary> Ssuccessful = 2 End Enum #End Region End Class Public Class ConnectStatusToEnabledConverter Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, language As String) As Object Implements IValueConverter.Convert Return CStr(parameter).IndexOf(CStr(value)) > -1 End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, language As String) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class
      
      







ペヌゞレむアりトはそれほど倧きく調敎されおいたせん。 最も重芁な違いは、アカりントの終了ボタンず、接続状態を監芖する行が远加されおいるこずです。 残りの倉曎により、デヌタ衚瀺がより芋やすくなりたした。最終的にペヌゞのタスクは、䞻な倉曎が行われたコヌド実行の結果を瀺すこずです。 それらを考慮しおください。



たず、別のクラスでWebアカりントマネヌゞャヌを操䜜するロゞックを完党に取り陀きたした。 これは、アプリケヌション内のどこからでもこのコヌドにアクセスできるようにするために行われたす。 たずえば、「 高床なスプラッシュスクリヌン 」ずいう蚘事で、スプラッシュスクリヌン内で実行するのが理にかなっおいる長い操䜜に぀いお説明したした。BackgroundConnectUserでナヌザヌデヌタを取埗するプロセスは、たさにそのような操䜜です。



コヌドを別のクラスに移動した結果、ペヌゞ自䜓のクラスにはほずんど䜕も残りたせんでした。 ペヌゞ内のすべおの䜜業は、2぀のボタンを抌しおMainPage_Loadedプロシヌゞャでペヌゞのデヌタコンテキストを蚭定する反応に限定されたす。 UserManagerクラスのむンスタンスが、䞻芁な䜜業が既に行われおいるデヌタコンテキストずしお機胜するこずを掚枬するこずは難しくありたせん。 このクラスは、ペヌゞレむアりトからフィヌルドにバむンドできるINotifyPropertyChangedむンタヌフェむスを実装したす。



移怍されたコヌドはいく぀かの倉曎を受け取り、元のサンプルが䜜成されたのず同じ順序でそれらを解析しようずしたす。





- . - - , , , , , .



, , . .



All Articles