Geeks With Blogs

News
Joe Mayo Cloud and Glue

The most recent release of LINQ to Twitter included support for Windows Phone. It’s important to note that only 7.1 is supported – the rationale being that 7.1 (Mango) introduced support for IQueryable, which LINQ to Twitter requires. This post will show you how to use LINQ to Twitter with Windows Phone.  You’ll see a normal public query, how to log in with OAuth, and how to post a tweet.  All of the code in this blog post is included with the LINQ to Twitter source code that you can download at http://linqtotwitter.codeplex.com.

Getting Started

The first thing you’ll need to do is create a new Windows Phone project or have an existing Windows Phone project for which you would like to add Twitter integration. When the project is open, you can use NuGet to add a reference to LINQ to Twitter.  Check out A Gentle Introduction to NuGet if you don’t have NuGet yet. Alternatively, you can visit the LINQ to Twitter site on CodePlex and download the Windows Phone version and create a file reference to the LinqToTwitterWP.dll assembly.

After setting up the project, the first task is to shape the data for binding to the UI.

The Model

The actual data shown on the screen is a tiny subset of what Twitter returns.  Here’s PublicTweet, which is the model object to display on screen:

namespace WindowsPhoneDemo
{
    public class PublicTweet
    {
        public string UserName { get; set; }

        public string Message { get; set; }

        public string ImageSource { get; set; }
    }
}

The application will create a collection of PublicTweet and bind each instance to the UI, discussed in the next section.

Making a Public Tweets Query

When you create a Windows Phone application, one of the files you get is MainPage.xaml, which is the default startup page. Below is the associated MainPage.xaml.cs, which contains a button click event handler that performs a query for the public timeline, using LINQ to Twitter:

using System.Linq;
using System.Windows;

using LinqToTwitter;
using Microsoft.Phone.Controls;

namespace WindowsPhoneDemo
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }

        private void PublicTimelineButton_Click(object sender, RoutedEventArgs e)
        {
            var ctx = new TwitterContext();

            (from tweet in ctx.Status
             where tweet.Type == StatusType.Public
             select tweet)
            .AsyncCallback(tweets =>
                Dispatcher.BeginInvoke(() =>
                {
                    var publicTweets =
                        (from tweet in tweets
                         select new PublicTweet
                         {
                             UserName = tweet.User.Identifier.ScreenName,
                             Message = tweet.Text,
                             ImageSource = tweet.User.ProfileImageUrl
                         })
                        .ToList();

                    PublicTweetListBox.ItemsSource = publicTweets;
                }))
            .SingleOrDefault();
        }
} }

To understand the query above, remember that all Internet communication in Windows Phone must be asynchronous (async). You don’t see a return value assigned from the query because that implies a blocking call that isn’t allowed.

In LINQ to Twitter, I solved the blocking problem with an idiom that relies on an extension method named AsyncCallback. Behind the scenes, LINQ to Twitter takes care of the plumbing associated with async communication and invokes the delegate, of type Action<IEnumerable<T>>, that you pass as a parameter to AsyncCallback.

In the code above, the AsyncCallback parameter is a lambda. The tweets parameter is IEnumerable<Status> because a LINQ to Twitter Status query of type StatusType.Public returns a collection of Status entities. Since the result is IEnumerable<T>, you can use LINQ to Objects to project the Status entities in to PublicTweet model instances.

Note: Don’t forget to used Dispatcher.BeginInvoke.  Invocation of AsyncCallback within LINQ to Twitter occurs on a thread other than the UI thread.  Therefore, you must marshal the thread back onto the UI thread to keep your application from crashing.

The example above assigns results of the query, projected into publicTweets, to the ItemsSource property of PublicTweetListBox.  Here’s the XAML from MainPage.xaml, showing you how the layout and binding for the Public Tweets query occurs:

<phone:PhoneApplicationPage 
    x:Class="WindowsPhoneDemo.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="Portrait" 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="LINQ to Twitter" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Public Tweets" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" FontSize="72" />
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Button Content="Get Public Timeline" Height="72" HorizontalAlignment="Left" Margin="51,66,0,0" Name="PublicTimelineButton" VerticalAlignment="Top" Width="365" Click="PublicTimelineButton_Click" />
            <ListBox Height="465" HorizontalAlignment="Left" Margin="5,144,0,0" Name="PublicTweetListBox" VerticalAlignment="Top" Width="451">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal" Height="132">
                            <Image Source="{Binding ImageSource}" Height="73" Width="73" VerticalAlignment="Top" Margin="0,10,8,0"/>
                            <StackPanel Width="370">
                                <TextBlock Text="{Binding UserName}" Foreground="#FFC8AB14" FontSize="28" />
                                <TextBlock Text="{Binding Message}" TextWrapping="Wrap" FontSize="24" />
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <HyperlinkButton Content="Status Update" Height="30" HorizontalAlignment="Left" Margin="122,6,0,0" Name="StatusUpdateHyperlink" VerticalAlignment="Top" Width="200" NavigateUri="/StatusUpdate.xaml" />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

In the XAML code above, you can see that the Button Click event is hooked up to the PublicTimeline_Click handler defined in the previous listing for the code-behind.

The DataTemplate for the ListBox specifies binding for properties exposed by the PublicTweets model.

Notice also that there’s a HyperlinkButton, providing navigation to StatusUpdate.xaml, which lets the user post a tweet. While public queries don’t require authentication, actions that occur on behalf of a user, such as posting tweets, must be authenticated, which I’ll discuss next.

Authenticating with OAuth

The Twitter API uses OAuth exclusively for authentication. There was a time when username/password authentication was supported, but proved to have too many security weaknesses which isn’t appropriate as a generalized solution for Web application communication scenarios.  Fortunately, LINQ to Twitter supports OAuth organically.

Tip: For more information on how LINQ to Twitter supports OAuth and additional references, you can visit the documentation pages on Securing Your Applications.

In this example, I’ll check to see if the user is authenticated before allowing them to tweet.  Here’s an excerpt from StatusUpdate.xaml.cs, the code behind for the Status Update page that allows you to tweet. It’s the button click handler that performs the tweet. What you see is the code that checks if the user is authenticated. I’ll show the actual tweet logic in the next section, but we’ll focus right now on the authentication logic:

        private void TweetButton_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(TweetTextBox.Text))
            {
                MessageBox.Show("Please enter text to tweet.");
            }

            ITwitterAuthorizer auth = SharedState.Authorizer;

            if (auth == null || !auth.IsAuthorized)
            {
                NavigationService.Navigate(new Uri("/OAuth.xaml", UriKind.Relative));
            }
            else
            {
                // UpdateStatus showing how to post a tweet omitted
                //   - delayed until next section of this blog post
            }
        }

After making sure the user typed some text, the code above assigns a reference to the application’s ITwitterAuthorizer, auth, which is an instance of a LINQ to Twitter type that manages OAuth.  SharedState is a static class that facilitates sharing the ITwitterAuthorizer between pages of the application, shown below:

using LinqToTwitter;

namespace WindowsPhoneDemo
{
    public static class SharedState
    {
        public static ITwitterAuthorizer Authorizer { get; set; }
    }
}

Looking back at the previous code, with the TweetButton_Click handler, notice that after checking that auth isn’t null, I call IsAuthenticated to see if the user authenticated yet. If false, the application navigates to OAuth.xaml for authentication.

Tip: At this point, you could also call your own business object that can instantiate and load credentials (that were saved previously) for the ITwitterAuthorizer.  This post shows how to instantiate the ITwitterAuthorizer and you can build upon that knowledge to make your application more flexible.

The code-behind in OAuth.xaml.cs contains methods to guide the authentication process. It sets up the proper events for the page, begins the authorization process, and handles completion of the authorization process.  The application uses Twitter’s PIN OAuth authorization process that includes the following steps: request authorization, get a pin number, submit the pin back to Twitter, and then receive authentication credentials for working with Twitter.  Here’s the code:

using System;
using System.Windows;
using System.Windows.Navigation;
using LinqToTwitter;
using Microsoft.Phone.Controls;

namespace WindowsPhoneDemo
{
    public partial class OAuth : PhoneApplicationPage
    {
        PinAuthorizer pinAuth;

        public OAuth()
        {
            InitializeComponent();
            Loaded += Page_Loaded;
            OAuthWebBrowser.LoadCompleted += OAuthWebBrowser_LoadCompleted;
        }

        void OAuthWebBrowser_LoadCompleted(object sender, NavigationEventArgs e)
        {
            EnterPinTextBlock.Visibility = Visibility.Visible;
            PinTextBox.IsEnabled = true;
            AuthenticateButton.IsEnabled = true;
        }

        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            pinAuth = new PinAuthorizer
            {
                Credentials = new InMemoryCredentials
                {
                    ConsumerKey = "",
                    ConsumerSecret = ""
                },
                UseCompression = true,
                GoToTwitterAuthorization = pageLink =>
                    Dispatcher.BeginInvoke(() => OAuthWebBrowser.Navigate(new Uri(pageLink, UriKind.Absolute)))
            };

            pinAuth.BeginAuthorize(resp =>
                Dispatcher.BeginInvoke(() =>
                {
                    switch (resp.Status)
                    {
                        case TwitterErrorStatus.Success:
                            break;
                        case TwitterErrorStatus.TwitterApiError:
                        case TwitterErrorStatus.RequestProcessingException:
                            MessageBox.Show(
                                resp.Error.ToString(),
                                resp.Message,
                                MessageBoxButton.OK);
                            break;
                    }
                }));
        }

        private void AuthenticateButton_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(PinTextBox.Text))
            {
                MessageBox.Show(
                   "Input Error",
                   "Please enter PIN",
                    MessageBoxButton.OK);

                return;
            }

            pinAuth.CompleteAuthorize(
                PinTextBox.Text,
                completeResp => Dispatcher.BeginInvoke(() =>
                {
                    switch (completeResp.Status)
                    {
                        case TwitterErrorStatus.Success:
                            SharedState.Authorizer = pinAuth;
                            NavigationService.GoBack();
                            break;
                        case TwitterErrorStatus.TwitterApiError:
                        case TwitterErrorStatus.RequestProcessingException:
                            MessageBox.Show(
                                completeResp.Error.ToString(),
                                completeResp.Message,
                                MessageBoxButton.OK);
                            break;
                    }
                }));
        }
    }
}

At the top of the listing, you see a PinAuthorizer field, pinAuth, which is type ITwitterAuthorizer and will be accessed via methods that begin and complete the authentication processs. The constructor adds handlers for Loaded and LoadCompleted events of the Page and WebBrowser, respectively. 

  1. The page has a WebBrowser control for user interaction with the OAuth authentication process and we can’t operate on that control properly until its LoadCompleted event.  The sequence of methods works like this:
  2. As soon as the program navigates to OAuth.xaml, the Page_Load handler begins the authentication process.
  3. The user will authorize the application and receive a pin that displays in the WebBrowser.
  4. The OAuthWebBrowser_LoadCompleted handler enables the TextBox where the user types the pin and turns on the button so the user can complete the authorization process.
  5. The AuthenticateButton_Click handler executes (when the user clicks AuthenticateButton).
  6. The code completes the authentication process, and the user is redirected back to the StatusUpdate.xaml page.

As mentioned previously, the whole process kicks off via the Page_Load method. You can see how the new PinAuthorizer is instantiated with a new Credentials instance. In this example, credentials are empty and you must write the code that populates ConsumerKey and ConsumerSecret properties. The GoToTwitterAuthorization is an Action<string> that allows an application to navigate to Twitter’s authentication Web page after the request is initialized.

Note: Having a solid grasp of the OAuth authentication process will reduce much confusion.  The amount of information for explaining the process is beyond the scope of this post, but is documented on the LINQ to Twitter site under Securing Your Applications.

Call BeginAuthorize on the ITwitterAuthorizer, pinAuth, to begin the authentication process. Since this requires async communication, the parameter is an Action<TwitterAsyncResponse<object>>. TwitterAsyncResponse is a LINQ to Twitter type that makes it easy for you to process results from Twitter. The TwitterErrorStatus enum helps you discern between causes of error or success during communication.

Tip: Since errors communicating with Twitter are common, best practice is to use TwitterErrorStatus for proper error handling.

Calling Dispatcher.BeginInvoke marshals the operation back onto the UI thread.

Reminder: As mentioned earlier, async responses from LINQ to Twitter occur on their own thread, so you’ll want to marshal operations back onto the UI thread as I did with Dispatcher.BeginInvoke.

As discussed earlier, after the authentication process begins and the user is authenticated, the user will enter a PIN in the PinTextBox and click AuthenticateButton. This invokes the AuthenticateButton_Click event handler, in the code above, executing the completion process.

When calling CompeteAuthorize on the ITwitterAuthorizer, pinAuth, pass the pin from PinTextBox and add a callback handler for the Twitter response. The second parameter for CompleteAuthorize is Action<TwitterAsyncResponse<UserIdentifier>>. In the call to BeginAuthorize, the TwitterAsyncResponse type parameter was object because it wasn’t used, but is UserIdentifier in CompleteAuthorize. The TwitterAsyncResponse type parameter defines the type returned in the Status property of the result, which is completeResp.Status in this case. This allows you to read ScreenName or UserID returned from Twitter if you need it. i.e. completeResp.Status.ScreenName.

Notice that the TwitterErrorStatus.Success case assigns pinAuth to SharedState.Authorizer so it’s accessible to the rest of the program. Then it goes back to the previous page, StatusUpdate.xaml, which is discussed next.

Posting a Tweet

Now that you’re authorized, you can tweet. This involves using the LINQ to Twitter UpdateStatus method, shown below:

using System;
using System.Globalization;
using System.Windows;
using LinqToTwitter;
using Microsoft.Phone.Controls;

namespace WindowsPhoneDemo
{
    public partial class StatusUpdate : PhoneApplicationPage
    {
        public StatusUpdate()
        {
            InitializeComponent();

            TweetTextBox.Text = "Windows Phone Test, " + DateTime.Now.ToString(CultureInfo.InvariantCulture) + " #linq2twitter";
        }

        private void TweetButton_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(TweetTextBox.Text))
            {
                MessageBox.Show("Please enter text to tweet.");
                return;
            }

            ITwitterAuthorizer auth = SharedState.Authorizer;

            if (auth == null || !auth.IsAuthorized)
            {
                NavigationService.Navigate(new Uri("/OAuth.xaml", UriKind.Relative));
            }
            else
            {
                var twitterCtx = new TwitterContext(auth);

                twitterCtx.UpdateStatus(TweetTextBox.Text,
                    updateResp => Dispatcher.BeginInvoke(() =>
                    {
                        switch (updateResp.Status)
                        {
                            case TwitterErrorStatus.Success:
                                Status tweet = updateResp.State;
                                User user = tweet.User;
                                UserIdentifier id = user.Identifier;
                                MessageBox.Show(
                                    "User: " + id.ScreenName +
                                    ", Posted Status: " + tweet.Text,
                                    "Update Successfully Posted.",
                                    MessageBoxButton.OK);
                                break;
                            case TwitterErrorStatus.TwitterApiError:
                            case TwitterErrorStatus.RequestProcessingException:
                                MessageBox.Show(
                                    updateResp.Error.ToString(),
                                    updateResp.Message,
                                    MessageBoxButton.OK);
                                break;
                        }
                    }));
            }
        }
    }
}

You saw the first part of the TweetButton_Click handler earlier when I explained how to begin the authentication process. Now look at the else clause that executes when authentication succeeds. To use the authentication credentials, pass the ITwitterAuthorizer instance, auth, to the TwitterContext constructor.

UpdateStatus is a method you can call through the TwitterContext instance, twitterCtx, to tweet. The first parameter is the text to tweet and the second parameter is an Action<TwitterAsyncResponse<Status>>. The lambda calls Dispatcher.BeginInvoke to marshal the operation back onto the UI thread.

Reminder: As mentioned earlier, async responses from LINQ to Twitter occur on their own thread, so you’ll want to marshal operations back onto the UI thread as I did with Dispatcher.BeginInvoke.

The updateResp parameter contains the result, which is a TwitterAsyncResponse<Status>. You can see in the TwitterErrorStatus.Success case of the switch statement where updateResp.Status is type Status, which is specified as the TwitterAsyncResponse type parameter.  Here’s the XAML from StatusUpdate.xaml:

<phone:PhoneApplicationPage 
    x:Class="WindowsPhoneDemo.StatusUpdate"
    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"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    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="LINQ to Twitter" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Status Update" 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">
            <Button Content="Tweet" Height="72" HorizontalAlignment="Left" Margin="143,127,0,0" Name="TweetButton" VerticalAlignment="Top" Width="160" Click="TweetButton_Click" />
            <TextBox Height="72" HorizontalAlignment="Left" Margin="0,49,0,0" Name="TweetTextBox" Text="" VerticalAlignment="Top" Width="467" />
            <HyperlinkButton Content="Public Timeline" Height="30" HorizontalAlignment="Left" Margin="119,6,0,0" Name="hyperlinkButton1" VerticalAlignment="Top" Width="200" NavigateUri="/MainPage.xaml" />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

The listing above shows that the screen has a TextBox for tweet input, a button for initiating the tweet, and a HyperlinkButton that allows navigation back to the Public Query page.

Summary

After learning how to get started with using LINQ to Twitter on a new Windows Phone project, you saw how public queries work.  The post followed up with an explanation of how to use LINQ to Twitter’s built-in OAuth authentication services, which was required for the subsequent explanation of how to post a tweet. You also learned how to use LINQ to Twitter’s async idioms and how to manage callbacks and the data returned.

@JoeMayo

Posted on Monday, March 5, 2012 7:38 AM Mobile | Back to top


Comments on this post: Using LINQ to Twitter with Windows Phone

No comments posted yet.
Your comment:
 (will show your gravatar)
 


Copyright © Joe Mayo | Powered by: GeeksWithBlogs.net | Join free