Sunday, December 2, 2012

Windows 8 + Salesforce.com Part 4

Over the past three posts I have shown how to build a Windows 8 Windows Store Application that will display the Account records from Salesforce.com. As the application is currently built the user needs to login to Salesforce each time it is run. In this post I will show how to keep the use logged in using a refresh token.
Before I continue  a note about the architecture of this demo. I had originally planned to build this using the Model-View-ViewModel (MVVM) design pattern which is very common to XAML applications, but decided for the sake of simplicity to just do a basic event driven architecture for now. At a later date I might do some posts on how to change this into an MVVM based application.
Ok, now on to the updates we are going to make to the program from Part 3. First we need to add a function to sfdcLibrary.cs to get an access token given a refresh token.

public async Task<bool> RefreshToken(string refreshToken)
{
    string URI = "https://login.salesforce.com/services/oauth2/token";
    StringBuilder body = new StringBuilder();

    body.Append("refresh_token=" + refreshToken + "&");
    body.Append("grant_type=refresh_token&");
    body.Append("client_id=" + clientID + "&");
    body.Append("client_secret=" + clientSecret + "&");
    body.Append("redirect_uri=" + redirectURL);
    var response = await HttpPost(URI, body.ToString());

    if (response.StatusCode != System.Net.HttpStatusCode.OK) return false;

    var data = await response.Content.ReadAsStringAsync();
    AccessToken = Deserialize<TokenResponse>(data);

    return true;
}

This function works just like the one we used to get the initial access token, but this one starts from a refresh token and uses that to get the access token. We are also going to change the AccessToken to a public property since we will need to reference it outside of the class.

public TokenResponse AccessToken { get; set; }

Now lets modify AccountsView.xaml to include a UI to login and logout.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
    <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{StaticResource AppName}" IsHitTestVisible="false" Style="{StaticResource PageHeaderTextStyle}"/>
    <StackPanel Grid.Column="2" Orientation="Vertical" Margin="10,10,10,10" >
        <Button Name="UILogout" HorizontalAlignment="Center" Click="UILogout_Click">Logout</Button>
        <Button Name="UILogin" HorizontalAlignment="Center" Click="UILogin_Click">Login</Button>
        <CheckBox Name="UIStayLoggedIn" HorizontalAlignment="Center">Stay Logged In</CheckBox>
    </StackPanel>
</Grid>

This is the XAML markup for the gird that forms the header of the page. I have added a third column to the grid to hold the controls. I also added a StackPanel with a Login button, Logout button and a check box to control whether the application should stay logged in or not.

To MainPage.xaml.cs we are going to add two functions to control the UI state.

private async void LoggedIn()
{
    UILogin.Visibility = Visibility.Collapsed;
    UIStayLoggedIn.Visibility = Visibility.Collapsed;
    UILogout.Visibility = Visibility.Visible;
    var acc = await App.SFDC.ReadAccounts();
    model.Accounts = acc.records;
}

This function sets the form to a logged in state. The UILogin and UIStayLoggedIn controls are hidden, UILogout control shown and the viewmodel loaded with a list of accounts. You will notice that in XAML Visibility isn’t just a Boolean, it actually has three states. Visible displays the controls, Hidden hides the control but still reserves space for it in the UI and Collapsed hides it and doesn’t reserve space for it. The second function is similar and sets a logged out state.


private void LoggedOut()
{
    UILogin.Visibility = Visibility.Visible;
    UILogout.Visibility = Visibility.Collapsed;
    UIStayLoggedIn.Visibility = Visibility.Visible;
    model.Accounts = null;
    App.SFDC.AccessToken = null;
}

Again we set the control visibility, clear the accounts from the model and clear the access token.  Next we need to change the LoadState function.


protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    LoggedOut();

    this.DataContext = model;

    if (ApplicationData.Current.LocalSettings.Values["refreshToken"] != null)
    {
        if (await App.SFDC.RefreshToken((string)ApplicationData.Current.LocalSettings.Values["refreshToken"])) LoggedIn();
    }
}

In this function we start by setting the UI to the logged out state and then set the data context to the viewmodel. In the next line we check the users local settings to see if we have saved a refresh token there. If we do have a saved token we use it call RefreshToken and if this is successful we set the logged in state.

Finally we need handlers for the Login and Logout button click events.

private async void UILogin_Click(object sender, RoutedEventArgs e)
{
    if (await App.SFDC.LoginSalesforce())
    {
        if ((bool)UIStayLoggedIn.IsChecked) ApplicationData.Current.LocalSettings.Values["refreshToken"] = App.SFDC.AccessToken.refresh_token;
        LoggedIn();
    }
}

When the login button is clicked we call the LoginSalesforce function which does the OAuth authentication. If the user has checked the stay logged in checkbox we store the refresh token that was returned as part of the access token in the users local settings so we can use it later to re-authenticate without the user having to re-enter their credentials. If the checkbox wasn’t checked we just login for this one session and the user will need to login again next time the application is run. Finally the Logout event handler.

private void UILogout_Click(object sender, RoutedEventArgs e)
{
    ApplicationData.Current.LocalSettings.Values.Remove("refreshToken");
    LoggedOut();
}

When the user logs out we clear the refresh token from the user’s configuration settings and set the form to the logged out state.

You can download the source for this version of the application here.

No comments: