VB Magic

2011/04/01

Client/Server experiment with MVC3 and Windows Phone 7 (Part 2.2 – Login Page)

Filed under: Learning — Tags: , , , , , , — vbmagic @ 3:59 pm

After getting over the “Add Service Reference” problem mentioned in the previous post, the Login page was created:

login page graphic

Login Page

XAML file below:

https://vbmagic.wordpress.com/wp-admin/edit.php
<phone:PhoneApplicationPage 
    x:Class="TyranntPhone.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="TYRANNT" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="login" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid x:Name="LoginGrid" Background="Transparent">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock x:Name="usernameTextBlock" Grid.Row="0" Grid.Column="0" Text="Username" VerticalAlignment="Center" />
                <TextBox x:Name="usernameTextBox" Grid.Row="0" Grid.Column="1" Text="" VerticalAlignment="Center" />
                <TextBlock x:Name="passwordTextBlock" Grid.Row="1" Grid.Column="0" Text="Password" VerticalAlignment="Center" />
                <PasswordBox x:Name="passwordPasswordBox" Grid.Row="1" Grid.Column="1" Password="" VerticalAlignment="Center" IsEnabled="False" />
                <CheckBox x:Name="rememberMeCheckBox" Grid.Row="2" Grid.Column="1" Content="Remember Me" IsEnabled="False" />
                <Button x:Name="loginButton" Grid.Row="3" Grid.ColumnSpan="2" Content="Login" IsEnabled="False" />
                <Button x:Name="logoutButton" Grid.Row="4" Grid.ColumnSpan="2" Content="Logout" Visibility="Collapsed" />
            </Grid>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

Then helper methods were created to adjust the page layout and to help with passing information between pages. Also, to help with the application tomb-stoning, the following routines were created in the App.xaml.vb page to store and retrieve information:

    Private Sub StoreDetails()
		' The PhoneApplicationService stores information while the application is "Active"
        Dim phoneAppService As PhoneApplicationService = PhoneApplicationService.Current
		' The IsolatedStorageSettings will store informaiton on the phones flash memory to be
		' retrieved after the app has shut down and re-started.
        Dim settings As IsolatedStorageSettings = IsolatedStorageSettings.ApplicationSettings

		' Pull the token GUID from temp storage in the phones memory and write into the 
		' permanante storage on the phone's flash disk. Only if it exists
        Dim token As Guid
        If phoneAppService.State.TryGetValue("token", token) = True Then
            settings("token") = token
        End If
		' The same for the remembered username, this will allow the name to be recalled
		' after the app has been shut down in any way.
        Dim rname As String = ""
        If phoneAppService.State.TryGetValue("rememberedUsername", rname) = True Then
            settings("rememberedUsername") = rname
        End If
		' This stores the user group, for the moment, this should only really be done
		' if the app is paused to allow another app to run. If the app is exited the
		' user will have to log on again anyway.
        Dim group As String = ""
        If phoneAppService.State.TryGetValue("group", group) = True Then
            settings("group") = group
        End If
    End Sub

    Private Sub GetDetails()
		' The PhoneApplicationService stores information while the application is "Active"
        Dim phoneAppService As PhoneApplicationService = PhoneApplicationService.Current
		' The IsolatedStorageSettings will store informaiton on the phones flash memory to be
		' retrieved after the app has shut down and re-started.
        Dim settings As IsolatedStorageSettings = IsolatedStorageSettings.ApplicationSettings

		' The token us used to identify the user without the need for a username and password.
        Dim token As Guid
        If settings.TryGetValue("token", token) = True Then
            phoneAppService.State("token") = token
        End If
		' if the user has ticked the remember me box, this is the name they used to log on.
        Dim rname As String = ""
        If settings.TryGetValue("rememberedUsername", rname) = True Then
            phoneAppService.State("rememberedUsername") = rname
        End If
		' this will show if the user is a member of any special user group such as administrator
        Dim group As String = ""
        If settings.TryGetValue("group", group) = True Then
            phoneAppService.State("group") = group
        End If
    End Sub

I got the idea for this from the WP7 course at http://learnvisualstudio.net a good resource for learning most things .net.

Here is the code to handle the enabling and disabling of sections of the login page:

Partial Public Class MainPage
    Inherits PhoneApplicationPage

    Private _phoneAppService As PhoneApplicationService = PhoneApplicationService.Current

    ' Constructor
    Public Sub New()
        InitializeComponent()
    End Sub

    Private Sub EnableLogin(ByVal state As Boolean)
        usernameTextBox.IsEnabled = state
        passwordPasswordBox.IsEnabled = state
        loginButton.IsEnabled = state
    End Sub

    Private Sub usernameTextBox_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs) Handles usernameTextBox.TextChanged
        If usernameTextBlock.Text.Length > 3 And passwordPasswordBox.IsEnabled = False Then
            passwordPasswordBox.IsEnabled = True
            rememberMeCheckBox.IsEnabled = True
        ElseIf usernameTextBox.Text.Length <= 3 And passwordPasswordBox.IsEnabled = True Then
            passwordPasswordBox.IsEnabled = False
        End If
    End Sub

    Private Sub passwordPasswordBox_PasswordChanged(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles passwordPasswordBox.PasswordChanged
        If passwordPasswordBox.Password.Length > 3 And loginButton.IsEnabled = False Then
            loginButton.IsEnabled = True
        ElseIf passwordPasswordBox.Password.Length <= 3 And loginButton.IsEnabled = True Then
            loginButton.IsEnabled = False
        End If
    End Sub

    Private Sub PhoneApplicationPage_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
        If RetrieveDetails() = True Then
            passwordPasswordBox.Focus()
            rememberMeCheckBox.IsChecked = True
        Else
            usernameTextBox.Focus()
        End If

    End Sub

    Private Sub PhoneApplicationPage_BackKeyPress(ByVal sender As System.Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.BackKeyPress
        Dim res As MessageBoxResult = MessageBox.Show("Are you sure you wish to exit", "Exit", MessageBoxButton.OKCancel)
        If res = MessageBoxResult.Cancel Then
            e.Cancel = True
        End If
    End Sub

    Private Sub StoreDetails()
        _phoneAppService.State("rememberedUsername") = usernameTextBox.Text
    End Sub

    Private Function RetrieveDetails() As Boolean
        Dim tmp As String = ""
        If _phoneAppService.State.TryGetValue("rememberedUsername", tmp) = False Then
            Return False
        Else
            usernameTextBox.Text = tmp
            Return True
        End If
    End Function

    Private Sub rememberMeCheckBox_Checked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles rememberMeCheckBox.Checked
            StoreDetails()
    End Sub

    Private Sub DeleteDetails()
        _phoneAppService.State.Remove("rememberedUsername")
    End Sub

    Private Sub rememberMeCheckBox_Unchecked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles rememberMeCheckBox.Unchecked
        DeleteDetails()
    End Sub

End Class

Now that this infrastructure is in place we get to the good bit, consuming the login service. The following routines were created to handle the login button click event and code to handle the call back for the login service.

    Private Sub loginButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles loginButton.Click
        Dim client As New userServiceReference.UserServiceClient
        AddHandler client.LoginCompleted, AddressOf client_LoginCompleted
        Dim login As New userServiceReference.LoginType
        login.Username = usernameTextBox.Text
        login.Password = passwordPasswordBox.Password
        client.LoginAsync(login)
        EnableLogin(False)
        If rememberMeCheckBox.IsChecked = True Then
            StoreDetails()
        End If
    End Sub

    Private Sub client_LoginCompleted(ByVal sender As Object, ByVal e As userServiceReference.LoginCompletedEventArgs)
        If e.Error Is Nothing Then
            Dim res As userServiceReference.TokenType = e.Result
            If res.errorMessage <> "" And res.errorMessage <> "fail" Then
                MessageBox.Show(res.errorMessage)
                EnableLogin(True)
            ElseIf res.errorMessage = "fail" Then
                MessageBox.Show("Username/Password incorrect")
                EnableLogin(True)
            Else
                _phoneAppService.State("token") = res.TokenGUID
                _phoneAppService.State("group") = res.UserGroup
                NavigationService.Navigate(New Uri("/pages/MemberPivotPage.xaml", UriKind.Relative))
                loginButton.Visibility = Windows.Visibility.Collapsed
                logoutButton.Visibility = Windows.Visibility.Visible
            End If
        Else
            MessageBox.Show(e.Error.Message)
            EnableLogin(True)
        End If
    End Sub

The contents of the username and password from the boxes on the page are used to generate a login type which is defined in the web service but I’ll re-show here:

<DataContract()>
Public Class LoginType

    <DataMember()>
    Public Property Username() As String

    <DataMember()>
    Public Property Password() As String
 
End Class

Then the callback routine to handle the completed login web service call was created and an event handler put into place. Then the ASYNC version of the service (Which is the only one available on Silverlight for Windows Phone) is called and the UI is changed to stop the user from trying to log in twice.
At the server end, the Membership service will attempt to validate the credentials. (A side note: To add a new user, you need to register on the website. Which is all handled automatically if you create an MVC3 website rather than a blank project.)

When the service returns the result of the login, firstly the app checks to see if the e.Error object has been set, this will indicate something went wrong with the service call and for now, it will just display the error message in a message box on the phone (Example of this is a timeout). Next the app will have a look at the returned object which is a TokenType which again is defined in the web service and I’ve shown again below:

<DataContract()>
Public Class TokenType

    <DataMember()>
    Public Property UserGroup() As String

    <DataMember()>
    Public Property TokenGUID() As Guid

    <DataMember()>
    Public Property errorMessage() As String

End Class

The errorMessage property of the TokenType class is used to pass back in the login failed for a username/password error. Basically if errorMessage is “” then everything went OK. If it contains the text “fail” then the username/password combination isn’t valid so the phone app will display that in a message box and reset the display to allow the user to attempt to log on again. If an exception occurred during the login process this is captured by the service and put into the errorMessage property. The phone app will check this and for now display that error message to aid in debugging.

If everything went well, then there should be a GUID in the TokenGUID property which will be used from that point onwards to identify that user. Later I will make every attempt to access the services change this GUID and pass the new one back. I will also add a time out so that the app can get the user to re-login after a period of inactivity has occurred.

If the login was successful we then enable a Logout button and collapse the login button and navigate to the “Member” section of the app using the following command:

NavigationService.Navigate(New Uri("/pages/MemberPivotPage.xaml", UriKind.Relative))

Next post on this experiment will be about what happens on this page.

Blog at WordPress.com.