MVC 4 Part 5 - Adding the Twitter API


MVC 4 Part 5 - Adding the Twitter API


If you are coding in .Net and want to access the Twitter API then this is for you. One handy element is that the code below includes a 'one liner' that will convert a JSON object directly into a dynamic C# object, nice!

I know that this is a slight diversion from MVC really. However, I thought it might be useful to share my experience in calling the Twitter REST 1.1 API from within the MVC application (this website in fact).

In the MVC Application (this website) I have created a Twiiter Class. The code shown below shows the basis of that class and, whilst not complete, it should be enough to get you going. There is an example of a couple of GET Methods and a POST method. In short, you will be able to get a list of Tweets and Mentions for the authenticated user and Post a Tweet on behalf of that user.

Authentication

Twitter uses OAuth, this is fairly simple to implement in .Net. All that is required is that a few components are in place.

For integrating Twitter into this Web Site, I logged on the Twitter Developer site (http://dev.twitter.com) and created an Application called "johnnewcombe.net" This gave me a set of keys. I was given an Access Token and its associated secret and a Consumer Key and its associated secret. Four values in total.

At this point it is worth pointing out that for my application I wanted to be able to create Tweets. This means that the Application has to have the appropriate permissions. This is easy to change in the Developer area but there is a GOTCHA!

When you create the Application in the Developer area the Consumer Key is set to 'Read Only'. This is easy to change to 'Read Write and Direct Messages'. However, if you do this you have to click the button at the bottom of the page to 'Recreate my Access Token'. This is fine BUT the page does not always get refreshed, meaning that the permissions are shown as changed but the new tokens are not visible. By refreshing the page manually you will be able to see the new tokens.

After all of this malarky you will end up with four text strings. These are what are needed for the website to access the Twitter API using OAuth 

Whilst I am discussing Gotchas! there is another. Make sure that the clock on your server/dev machine is set to the same as Twitters otherwise you will get the Access Denied 401 message. Actually there is a neat trick you could do here. The returned exception includes the time, Twitters time, so you could catch the exception and retry the request with the time returned in the exception.

The Code

The Twitter class as described later, is extremely easy to use. The example below shows how to get the last five of my Tweets. Note that in this implementation I have included Latitude and Longitude for the Tweet. As these are optional, your implementation could be even simpler.

var twitter = new Twitter(MvcApplication.OauthConsumerKey, MvcApplication.OauthConsumerKeySecret, MvcApplication.OauthAccesToken, MvcApplication.OauthAccessTokenSecret); //twitter.PostStatusUpdate(status, 54.35,-0.2); var response = twitter.GetTweets("johnnewcombeuk",5);

We create the class passing in our four keys/secrets and simply call the method we want. Result is a string containing a JSON object. To make this into a C# object all that is required is to convert it using classes in the MVC WebHelpers namespace. For example:

dynamic timeline = System.Web.Helpers.Json.Decode(response);

Using the Dynamic c# object we can get at the properties of the Tweet. For example:

foreach (var tweet in timeline) { string text = timeLineMention["text"].ToString(); model.Timeline.Add(text); }

In the above case I have created a View Model that has a property called Timeline of type List<string> to hold the text of each tweet this is used in a Partial View that is dragged into the footer. If you look at the bottom of this page, you will see the results. Easy peasy!

Adding a new method is easy, for example to add the SendDirectMessage() method we simply add code similar to the following:

public string SendDirectMessage(string screenName, string text) { string resourceUrl = string.Format("https://api.twitter.com/1.1/direct_messages/new.json"); var requestParameters = new SortedDictionary<string, string>(); requestParameters.Add("screen_name", screenName); requestParameters.Add("text", text); var response = GetResponse(resourceUrl, Method.POST, requestParameters); return response; }

The Class Implementation

Here is the code for the basic Class to use the REST API. It handles the OAuth stuff and each Method returns a JSON object as a string. It is not mean't to be a complete implementation but will certainly get you going. Two GET Methods and a POST method are shown.

Points to note are that the requestParameters are stored in a Sorted Dictionary as parameters need to be in Alphabetical order for the API requests to be accepted. The other thing to note is that I have extended the SortedDictionary Class to add a 'ToWebString() method. Although a rubbish name, it is a great help in creating a query string, a POST body and OAuth signatures. I am sure you can think of a better name.

Enjoy!

using System; using System.IO; using System.Net; using System.Text; using System.Diagnostics; using System.Collections.Generic; using System.Security.Cryptography; namespace WebUI.Code { public class Twitter { public const string OauthVersion = "1.0"; public const string OauthSignatureMethod = "HMAC-SHA1"; public Twitter(string consumerKey, string consumerKeySecret, string accessToken, string accessTokenSecret) { this.ConsumerKey = consumerKey; this.ConsumerKeySecret = consumerKeySecret; this.AccessToken = accessToken; this.AccessTokenSecret = accessTokenSecret; } public string ConsumerKey { set; get; } public string ConsumerKeySecret { set; get; } public string AccessToken { set; get; } public string AccessTokenSecret { set; get; } public string GetMentions(int count) { string resourceUrl = string.Format("http://api.twitter.com/1/statuses/mentions.json"); var requestParameters = new SortedDictionary<string, string>(); requestParameters.Add("count", count.ToString()); requestParameters.Add("include_entities", "true"); var response = GetResponse(resourceUrl, Method.GET, requestParameters); return response; } public string GetTweets(string screenName, int count) { string resourceUrl = string.Format("https://api.twitter.com/1.1/statuses/user_timeline.json"); var requestParameters = new SortedDictionary<string, string>(); requestParameters.Add("count", count.ToString()); requestParameters.Add("screen_name", screenName); var response = GetResponse(resourceUrl, Method.GET, requestParameters); return response; } public string PostStatusUpdate(string status, double latitude, double longitude) { const string resourceUrl = "http://api.twitter.com/1/statuses/update.json"; var requestParameters = new SortedDictionary<string, string>(); requestParameters.Add("status", status); requestParameters.Add("lat", latitude.ToString()); requestParameters.Add("long", longitude.ToString()); return GetResponse(resourceUrl, Method.POST, requestParameters); } private string GetResponse(string resourceUrl, Method method, SortedDictionary<string, string> requestParameters) { ServicePointManager.Expect100Continue = false; WebRequest request = null; string resultString = string.Empty; if (method == Method.POST) { var postBody = requestParameters.ToWebString(); request = (HttpWebRequest) WebRequest.Create(resourceUrl); request.Method = method.ToString(); request.ContentType = "application/x-www-form-urlencoded"; using (var stream = request.GetRequestStream()) { byte[] content = Encoding.ASCII.GetBytes(postBody); stream.Write(content, 0, content.Length); } } else if (method == Method.GET) { request = (HttpWebRequest)WebRequest.Create(resourceUrl + "?" + requestParameters.ToWebString()); request.Method = method.ToString(); } else { //other verbs can be addressed here... } if (request != null) { var authHeader = CreateHeader(resourceUrl, method, requestParameters); request.Headers.Add("Authorization", authHeader); var response = request.GetResponse(); using (var sd = new StreamReader(response.GetResponseStream())) { resultString = sd.ReadToEnd(); response.Close(); } } return resultString; } private string CreateOauthNonce() { return Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString())); } private string CreateHeader(string resourceUrl, Method method, SortedDictionary<string, string> requestParameters) { var oauthNonce = CreateOauthNonce(); var oauthTimestamp = CreateOAuthTimestamp(); var oauthSignature = CreateOauthSignature(resourceUrl, method, oauthNonce, oauthTimestamp, requestParameters); //The oAuth signature is then used to generate the Authentication header. const string headerFormat = "OAuth oauth_nonce=\"{0}\", oauth_signature_method=\"{1}\", " + "oauth_timestamp=\"{2}\", oauth_consumer_key=\"{3}\", " + "oauth_token=\"{4}\", oauth_signature=\"{5}\", " + "oauth_version=\"{6}\""; var authHeader = string.Format(headerFormat, Uri.EscapeDataString(oauthNonce), Uri.EscapeDataString(OauthSignatureMethod), Uri.EscapeDataString(oauthTimestamp), Uri.EscapeDataString(ConsumerKey), Uri.EscapeDataString(AccessToken), Uri.EscapeDataString(oauthSignature), Uri.EscapeDataString(OauthVersion) ); return authHeader; } private string CreateOauthSignature(string resourceUrl, Method method, string oauthNonce, string oauthTimestamp, SortedDictionary<string, string> requestParameters) { //firstly we need to add the standard oauth parameters to the sorted list requestParameters.Add("oauth_consumer_key", ConsumerKey); requestParameters.Add("oauth_nonce", oauthNonce); requestParameters.Add("oauth_signature_method", OauthSignatureMethod); requestParameters.Add("oauth_timestamp", oauthTimestamp); requestParameters.Add("oauth_token", AccessToken); requestParameters.Add("oauth_version", OauthVersion); var sigBaseString = requestParameters.ToWebString(); var signatureBaseString = string.Concat(method.ToString(), "&", Uri.EscapeDataString(resourceUrl), "&", Uri.EscapeDataString(sigBaseString.ToString())); //Using this base string, we then encrypt the data using a composite of the //secret keys and the HMAC-SHA1 algorithm. var compositeKey = string.Concat(Uri.EscapeDataString(ConsumerKeySecret), "&", Uri.EscapeDataString(AccessTokenSecret)); string oauthSignature; using (var hasher = new HMACSHA1(Encoding.ASCII.GetBytes(compositeKey))) { oauthSignature = Convert.ToBase64String( hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString))); } return oauthSignature; } private static string CreateOAuthTimestamp() { var nowUtc = DateTime.UtcNow; var timeSpan = nowUtc - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); var timestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString(); return timestamp; } } public enum Method { POST, GET } public static class Extensions { public static string ToWebString(this SortedDictionary<string, string> source) { var body = new StringBuilder(); foreach (var requestParameter in source) { body.Append(requestParameter.Key); body.Append("="); body.Append(Uri.EscapeDataString(requestParameter.Value)); body.Append("&"); } //remove trailing '&' body.Remove(body.Length - 1, 1); return body.ToString(); } } } }

Replacing the Twitter URLs for Links

The tweets returned through the api often include URLs, naturally these are sanitised and simply appear as strings. I particularly wanted to replace these with the appropriate link. I use a simple RegEx to do this, the function is shown below. Simply pass in the text of the tweet and you should get it back with all of the anchor tags in place. Remember to use the @Html.Raw() helper method in your view to ensure that these are rendered correctly.

public static string PopulateTweetLinks(string tweetText) { string regexHtHyperLink = @"(http|ftp|https)://([\w+?\.\w+])+([a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?"; var urlRx = new Regex(regexHtHyperLink, RegexOptions.IgnoreCase); MatchCollection matches = urlRx.Matches(tweetText); return matches.Cast().Aggregate(tweetText, (current, match) => current.Replace(match.Value, string.Format("({1})", match.Value, match.Value))); }


Leave a Comment on Google+

Get in Touch

If you require further information regarding any of my services, or just want to get in touch, please feel free to call. Alternatively, complete the following form.

Thank you, your message has been sent. I will do my best to get back to you within 24 hours. If you have not heard from me within 48hrs, please feel free to contact me again.

Unfortunately due to what appears to be a technical error, your message has not been sent.

However, the good news is that I can still be contacted via Google+, Twitter, LinkedIn and telephone.

  • Belmont Wharf, Skipton, North Yorkshire.
  • 07729 931645
  • @johnnewcombeuk
Captcha