Posting Twitter Status Update from Windows Phone

Posting Twitter Status Update from Windows Phone

In my post Twitter in a Windows Phone 7 App across at BuildMobile I covered how to authenticate against Twitter using OAuth1. What I didn’t cover in that post is how you can then use the Access Token to interact with the Twitter API. In this post I’ll show you how you can post a status update to Twitter from your Windows Phone application.

Let’s start by creating a TextBox and a Button. Obviously the TextBox will be for the status to be posted and the Button will be to submit the Tweet:

<TextBox x_Name="TweetText"/>
<Button Content="Tweet" Click="TweetClick" />

Some points/gotchas to note:

* The user will have had to been authenticated prior to hitting this button or the post will fail.
* Twitter does not allow the same message to be repeatedly posted – if during testing you attempt to post the same test message, don’t be surprised if it fails!
* If your Windows Phone application is going to post messages to Twitter it need to request Read/Write (and potentially Direct Message) permissions to the users Twitter account.  This is not enabled by default when you create the application in Twitter, you have to go to the Settings tab and adjust the Application Type eg

image

Essentially in order to post a status update to Twitter you simply need to create the appropriately formed HttpWebRequest. The request will be a POST and you’ll include “status=<new status>” as the body of the POST. The url of the request will be “statuses/update.json” off the Twitter base API url. Note that the .json indicates that the response will come back as json. You can specify .xml if you’d prefer an XML response.

private void TweetClick(object sender, RoutedEventArgs e) {
    var requestParameters = new Dictionary<string, string>();
    var body = "status=" + UrlEncode(this.TweetText.Text);
    requestParameters[OAuthPostBodyKey] = body;

    var postUrl = "http://api.twitter.com/1/statuses/update.json";
    var request = CreateRequest("POST", postUrl, requestParameters);
    request.BeginGetRequestStream(reqresult => {
        var req = reqresult.AsyncState as HttpWebRequest;
        using (var strm = req.EndGetRequestStream(reqresult))
        using (var writer = new StreamWriter(strm)) {
            writer.Write(body);
        }
        req.BeginGetResponse(result => {
            try {
                var req2 = result.AsyncState as HttpWebRequest;
                if (req2 == null) throw new ArgumentNullException("result", "Request parameter is null");
                using (var resp = req.EndGetResponse(result))
                using (var strm = resp.GetResponseStream())
                using (var reader = new StreamReader(strm)) {
                    var responseText = reader.ReadToEnd();

                  Dispatcher.BeginInvoke(() => MessageBox.Show("Tweeted!"));
                }
            }
            catch {
                Dispatcher.BeginInvoke(() => MessageBox.Show("Unable to tweet"));
            }
        }, req);
    }, request);
}

If you followed the code on my BuildMobile post there are a couple of changes we need to make in order to support posting. The first enables us to pass in a Dictionary into the CreateRequest method. This is so that we can specify the body of the post which needs to be used in the calculation of the signature.

private WebRequest CreateRequest(string httpMethod, string requestUrl, IDictionary<string, string> requestParameters = null) {
    if (requestParameters == null) {
        requestParameters = new Dictionary<string, string>();
    }
    var secret = "";
    if (!string.IsNullOrEmpty(token)) {
        requestParameters[OAuthTokenKey] = token;
    secret = tokenSecret;
    }
    if (!string.IsNullOrEmpty(pin)) {
        requestParameters[OAuthVerifierKey] = pin;
    }
    var url = new Uri(requestUrl);
    var normalizedUrl = requestUrl;
    if (!string.IsNullOrEmpty(url.Query)) {
    normalizedUrl = requestUrl.Replace(url.Query, "");
    }
    var signature = GenerateSignature(httpMethod, normalizedUrl, url.Query, requestParameters, secret);
    requestParameters[OAuthSignatureKey] = UrlEncode(signature);

    var request = WebRequest.CreateHttp(normalizedUrl);
    request.Method = httpMethod;
    request.Headers[HttpRequestHeader.Authorization] = GenerateAuthorizationHeader(requestParameters);
    return request;
}

The other change we need to make is to the GenerateAuthorizationHeader method. Because the status itself will be passed as a form parameter in the body of the POST it shouldn’t be included in the authorization header. As such we need to modify the method to ignore any parameters that do not begin with “oauth_”.

public static string GenerateAuthorizationHeader(IDictionary<string, string> requestParameters)
{     var paras = new StringBuilder();     foreach (var param in requestParameters)     {
        if (!param.Key.StartsWith("oauth_")) continue;
        if (paras.Length > 0) paras.Append(",");         paras.Append(param.Key + "="" + param.Value + """);     }     return "OAuth " + paras;
}

 

And there we have it. Code that will enable your Windows Phone application to post to Twitter.

Windows Live Id Authentication using Messenger Connect for Windows Phone

Windows Live Id Authentication using Messenger Connect for Windows Phone

If you saw my post on BuildMobile.com you would have seen that I used the “token” response_type when initially specifying the login url. This works well for getting a short lived access token but the wl.offline_access scope is effectively ignored – the expiry time comes back as 3600. This is completely useless for a Windows Phone application where you don’t want to have to reprompt the user every time they run the application. Luckily, despite the failings in the current implementation, there is a work around which involves a few more steps and uses the “code” response_type.

I’m not going to step through the entire process, instead, here is the code that you should be able to use – it’s based on my post Using Windows Live ID in a WP7 Appon BuildMobile.com so should be easy enough to follow.

Notes:
– You need to replace both <your_client_id> and <your_client_secret> with the corresponding values for your application
– In a production application you should not have the client id or client secret in plain text in your application – ideally this should be held on a server and only exposed to the client app over ssl and never persisted in the client app. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Windows;
using System.Windows.Navigation;

namespace WLTest
{
    public partial class MainPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }

        private void AuthenticateClick(object sender, RoutedEventArgs e)
        {
            var uriParams = new Dictionary<string, string>()
                                {
                                    {“client_id”, “<your_client_id>”},
                                    {“response_type”, “code”},
                                    {“scope”, “wl.signin,wl.basic,wl.offline_access”},
                                    {“redirect_uri”, “https://oauth.live.com/desktop”},
                                    {“display”, “touch”}
                                };
            StringBuilder urlBuilder = new StringBuilder();
            foreach (var current in uriParams)
            {
                if (urlBuilder.Length > 0)
                {
                    urlBuilder.Append(“&”);
                }
                var encoded = HttpUtility.UrlEncode(current.Value);
                urlBuilder.AppendFormat(“{0}={1}”, current.Key, encoded);
            }
            var loginUrl = “https://oauth.live.com/authorize?” + urlBuilder.ToString();

            AuthenticationBrowser.Navigate(new Uri(loginUrl));
            AuthenticationBrowser.Visibility = Visibility.Visible;

        }

        public string AccessToken { get; set; }
        public string RefreshToken { get; set; }

        private void BrowserNavigated(object sender, NavigationEventArgs e)
        {
            if (e.Uri.AbsoluteUri.ToLower().Contains(“https://oauth.live.com/desktop”))
            {
                var query = (from pair in e.Uri.Query.Trim(‘?’).Split(‘&’)
                             let bits = pair.Split(‘=’)
                             where bits.Length == 2
                             select new KeyValuePair<string, string>(bits[0], bits[1])).ToArray();

                var code =
                    query.Where(kvp => kvp.Key == “code”).Select(kvp => HttpUtility.UrlDecode(kvp.Value)).FirstOrDefault
                        ();
                if (string.IsNullOrEmpty(code))
                {
                    var error =
                        query.Where(kvp => kvp.Key == “error”).Select(kvp => HttpUtility.UrlDecode(kvp.Value)).
                            FirstOrDefault();
                    var error_desc =
                        query.Where(kvp => kvp.Key == “error_description”).Select(
                            kvp => HttpUtility.UrlDecode(kvp.Value)).FirstOrDefault();
                    MessageBox.Show(“Error: ” + error + “n” + error_desc);
                    AuthenticationBrowser.Visibility = System.Windows.Visibility.Collapsed;
                    return;
                }

                var uriParams = new Dictionary<string, string>()
                                    {
                                        {“client_id”, “<your_client_id>”},
                                        {“client_secret”, “<your_client_secret>”},
                                        {“redirect_uri”, “https://oauth.live.com/desktop”},
                                        {“code”, HttpUtility.UrlEncode(code)},
                                        {“grant_type”, “authorization_code”}
                                    };
                StringBuilder urlBuilder = new StringBuilder();
                foreach (var current in uriParams)
                {
                    if (urlBuilder.Length > 0)
                    {
                        urlBuilder.Append(“&”);
                    }
                    var encoded = HttpUtility.UrlEncode(current.Value);
                    urlBuilder.AppendFormat(“{0}={1}”, current.Key, encoded);
                }
                var tokenUri = “https://oauth.live.com/token?” + urlBuilder.ToString();

                var request = HttpWebRequest.CreateHttp(tokenUri);
                request.BeginGetResponse(result =>
                                             {
                                                 var req = result.AsyncState as HttpWebRequest;
                                                 using (var resp = req.EndGetResponse(result))

                                                 using (var strm = resp.GetResponseStream())
                                                 {
                                                     var serializer =
                                                         new DataContractJsonSerializer(
                                                             typeof (WindowsLiveAuthorizationCode));
                                                     var authorization =
                                                         serializer.ReadObject(strm) as WindowsLiveAuthorizationCode;
                                                     AccessToken = authorization.AccessToken;
                                                     RefreshToken = authorization.RefreshToken;

                                                     this.Dispatcher.BeginInvoke(() => MessageBox.Show(
                                                         “Access granted”));
                                                     RequestUserProfile();
                                                 }
                                             }, request);

                AuthenticationBrowser.Visibility = System.Windows.Visibility.Collapsed;
            }
          
        }

 

        private void RequestUserProfile()
        {
            var profileUrl = string.Format(“https://apis.live.net/v5.0/me?access_token={0}”,
                                           HttpUtility.UrlEncode(AccessToken));
            var request = HttpWebRequest.Create(new Uri(profileUrl));
            request.Method = “GET”;
            request.BeginGetResponse(result =>
                                         {
                                             try
                                             {
                                                 var resp = (result.AsyncState as HttpWebRequest).EndGetResponse(result);
                                                 using (var strm = resp.GetResponseStream())
                                                 {
                                                     var serializer =
                                                         new DataContractJsonSerializer(typeof (WindowsLiveProfile));
                                                     var profile =
                                                         serializer.ReadObject(strm) as WindowsLiveProfile;
                                                     this.Dispatcher.BeginInvoke((Action<WindowsLiveProfile>) ((user) => {
                                                                            this.UserIdText.Text = user.Id;
                                                                            this.UserNameText.Text = user.Name;
                                                                                                                   }),
                                                                                 profile);
                                                 }
                                             }
                                             catch (Exception ex)
                                             {
                                                 this.Dispatcher.BeginInvoke(() =>
                                                                             MessageBox.Show(“Unable to attain profile information”));
                                             }
                                         }, request);
        }

        [DataContract]
        public class WindowsLiveProfile
        {
            [DataMember(Name = “id”)]
            public string Id { get; set; }

            [DataMember(Name = “name”)]
            public string Name { get; set; }
        }

        [DataContract]
        public class WindowsLiveAuthorizationCode
        {
            [DataMember(Name = “access_token”)]
            public string AccessToken { get; set; }

            [DataMember(Name = “refresh_token”)]
            public string RefreshToken { get; set; }

            [DataMember(Name = “scope”)]
            public string Scope { get; set; }

            [DataMember(Name = “token_type”)]
            public string TokenType { get; set; }

            [DataMember(Name = “expires_in”)]
            public string ExpiresIn { get; set; }
        }

        private void RefreshTokenClick(object sender, RoutedEventArgs e)
        {

            var uriParams = new Dictionary<string, string>()
                                {
                                    {“client_id”, “<your_client_id>”},
                                    {“client_secret”, “<your_client_secret>”},
                                    {“redirect_uri”, “https://oauth.live.com/desktop”},
                                    {“refresh_token”, RefreshToken},
                                    {“grant_type”, “refresh_token”}
                                };
            StringBuilder urlBuilder = new StringBuilder();
            foreach (var current in uriParams)
            {
                if (urlBuilder.Length > 0)
                {
                    urlBuilder.Append(“&”);
                }
                var encoded = HttpUtility.UrlEncode(current.Value);
                urlBuilder.AppendFormat(“{0}={1}”, current.Key, encoded);
            }
            var tokenUri = “https://oauth.live.com/token?” + urlBuilder.ToString();

            var request = HttpWebRequest.CreateHttp(tokenUri);
            request.BeginGetResponse(result =>
                                         {
                                             var req = result.AsyncState as HttpWebRequest;
                                             using (var resp = req.EndGetResponse(result))

                                             using (var strm = resp.GetResponseStream())
                                             {
                                                 var serializer =
                                                     new DataContractJsonSerializer(
                                                         typeof (WindowsLiveAuthorizationCode));
                                                 var authorization =
                                                     serializer.ReadObject(strm) as WindowsLiveAuthorizationCode;
                                                 AccessToken = authorization.AccessToken;
                                                 RefreshToken = authorization.RefreshToken;

                                                 this.Dispatcher.BeginInvoke(() => MessageBox.Show(
                                                     “Token refreshed”));
                                             }
                                         }, request);
        }
    }
}