Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C#

Taming the Twitter API in C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
7 Aug 2017MIT3 min read 26.6K   7   3
Taming the Twitter API in C#

Introduction

This MVC Web application accesses the Twitter API and shows a list of tweets within the standard footer displayed on most pages. This was accomplished by accessing the Twitter API. This brief article describes the approach I took in order that it may help others.

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 to the Twitter Developer site (http://dev.twitter.com) and created an Application called “johnnewcombe.net”. 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.

C#
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:

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

Naturally, any JSON parser could be used.

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

C#
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!

The 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 meant 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!

C#
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();
            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();
            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();
            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 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 requestParameters)
        {
            var oauthNonce = CreateOauthNonce();
                // Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
            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 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 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();
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
United Kingdom United Kingdom
John is the author of the free Genetic Algorithm Framework for .Net (GAF) and the series of GeoUK NuGet packages. John studied at Leicester De Montfort University and gained a Distinction for the highly regarded Masters Degree in Computational Intelligence and Robotics. John can provide commercial product support for the GAF or GAF based projects and can be contacted via the Code Project, LinkedIn or the usual social channels.

Comments and Discussions

 
PraiseThanks a lot Pin
mathew-127-Dec-20 4:27
mathew-127-Dec-20 4:27 
QuestionThe underlying connection was closed: An unexpected error occurred on a send. Pin
dr ta3-Aug-20 6:09
dr ta3-Aug-20 6:09 
Questionerror error CS0305: Using the generic type 'SortedDictionary<TKey, TValue>' requires 2 type arguments Pin
hobnob30-Oct-18 4:15
hobnob30-Oct-18 4:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.