Authorization with LINQ to Twitter

Jun 14, 2011 at 5:04 PM

Hello Joe,

I have been using Tweetsharp and I am looking to move to LINQ to Twitter. However, I am having problems finding out the appropriate LINQ to Twitter documentation on how to provide my application's consumer and token data so that it can be authorized. I've looked at the source code for some examples, but I am a bit confused.

TweetSharp's way of instantiating this was simple, through one constructor which took the consumer key, consumer secret, token, and token secret.

How is this achieved using LINQ to Twitter? If you can point me in the right direction, it would be much appreciated.


Thanks!!

Coordinator
Jun 14, 2011 at 5:09 PM

Hi,

Yeah, I know I have work to do on docs. :)  It depends on what kind of app you're building. i.e. desktop (Console, WinForm, WPF), Web Forms, MVC, or Silverlight (OOB or Web).  What type of app are you doing?

Joe

Jun 14, 2011 at 5:13 PM

The application I am building is going to be a console application. Thanks!

Coordinator
Jun 14, 2011 at 6:08 PM
Edited Jun 14, 2011 at 6:08 PM

In this case, you have three authorization choices: pin, single user or XAuth.  Twitter restricts access to XAuth, so going that route might not work, but it's easier to do.  The most likely scenario you have is Pin auth (and possibly single user).  Once you know Pin auth, the single user scenario is even easier and there's an example of how to use it in the Program.cs in the LinqToTwitterDemo project.  Here's how to do the Pin auth:

Note: You will always need to do the full OAuth dance for user credentials the first time, which gets you the user's OAuthToken and AccessToken.  Subsequently, you can store the user's token (likely in a DB) and reuse them forever.  Twitter tokens don't expire.

1. Create a PinAuthorizer instance, with credentials, like this:

            // configure the OAuth object
            var auth = new PinAuthorizer
            {
                Credentials = new InMemoryCredentials
                {
                    ConsumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"],
                    ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"]
                },
                UseCompression = true,
                GoToTwitterAuthorization = pageLink => Process.Start(pageLink),
                GetPin = () =>
                {
                    // this executes after user authorizes, which begins with the call to auth.Authorize() below.
                    Console.WriteLine("\nAfter you authorize this application, Twitter will give you a 7-digit PIN Number.\n");
                    Console.Write("Enter the PIN number here: ");
                    return Console.ReadLine();
                }
            };

Granted, there's a lot of code in there, but it's designed to be flexible and extensible, giving you the maximum amount of control.  Take the Credentials, for example, and notice that I'm using an instance of InMemoryCredentials.  The credentials property itself is an IOAuthCredentials, meaning that you can implement that interface and create your own credential store to increase security or add code that automatically loads credentials behind the scenes from your DB.  In this example, I'm only grabbing the ConsumerKey and ConsumerSecret, which are required for starting the OAuth dance.  Consistent with my note above, you could also set a pre-stored OAuthToken and AccessToken.

UseCompression is an optional property that can reduce network traffic and improve performance by compressing communication payloads.

GoToTwitterAuthorization is a property that takes a lambda, allowing you to launch your browser.  You can use the code above, exactly as is or extend it for your own purposes.  i.e. What if you were doing a WPF app and wanted to use a control to navigate to the Twitter Web page via a form control?  Leaving it as is will work fine for a Console app.

The GetPin allows you to collect the PIN that Twitter gives the user after authorization.  Since you're running on the desktop, there isn't an obvious way for Twitter to redirect back to your application, so there needs to be a way to let your application know that the user has authorized it and for the application to prove that it is authorized when going back to Twitter for the final OAuthToken/AccessToken.  This is another point of extensibility where an example of an another implementation might be a WPF application that obtains the pin via a pop up dialog box.

If this seems hard, you can simplify it by either deriving from PinAuthorizer and implementing your common code or writing a custom authorizer that derives from OAuthAuthorizer and implements ITwitterAuthorizer, such as shown by the PinAuthorizer signature below:

public class PinAuthorizer : OAuthAuthorizer, ITwitterAuthorizer

2. After you've created an instance of ITwitterAuthorizer, PinAuthorizer in this case, kick off the authorization process, like this:

            auth.Authorize();

3. Next, pass the authorizer to an instance of TwitterContext so that all the queries are automatically decorated with information to prove to Twitter that they're authorized:

            using (var twitterCtx = new TwitterContext(auth, "https://api.twitter.com/1/", "https://search.twitter.com/"))
            {

The using statement ensures the TwitterContext, much like a DataContext or ObjectContext, is properly disposed.  The parameters are the PinAuthorizer instance, described above, and base urls for normal and search queries.  Search queries use a different URL.  The URLs are another point of extensibility because Twitter has changed them in the past and you might want to try using LINQ to Twitter with another service, such as Yammer, Tumblr, or WordPress, that implements the Twitter API.

Now that you have a TwitterContext, you can just query:

                twitterCtx.Log = Console.Out;

                var users =
                    (from tweet in twitterCtx.User
                     where tweet.Type == UserType.Friends &&
                           tweet.ScreenName == "JoeMayo"
                     select tweet)
                    .ToList();

                users.ForEach(user =>
                {
                    var status =
                        user.Protected || user.Status == null ?
                            "Status Unavailable" :
                            user.Status.Text;

                    Console.WriteLine(
                        "ID: {0}, Name: {1}\nLast Tweet: {2}\n",
                        user.Identifier.UserID, user.Identifier.ScreenName, status);
                });

The Log property is a property that you can assign a TextWriter to for sending diagnostic code to a stream. Here you have a normal LINQ query.  The only difference is a LINQ to Twitter idiom that uses a Type filter, in this case specifying to make this a query for Friend from the User entity.  The rest of the code demonstrates that your response comes back as an object list that either holds a collection or single object, depending on the type of query.

Note: Identifying user information comes back through the Identifier property of the returned User object.  This is necessary to separate the value you used to make the query from the value that has been returned.

Please ask questions and poke holes; I might begin using this as the start of my OAuth documentation.

Joe

Jun 14, 2011 at 6:56 PM

Thanks Joe for the examples.

Using the PinAuthorizer forces the application to open a web browser and for me to provide a pin number. This extra step is what I am attempting to avoid since I want the console application to automatically to be authorized without getting a pin number.

You mentioned that I could set the OAuthToken and AccessToken. If I do set these properties in an instance of ITwitterAuthorizer, then will this solve my issue?

 

Coordinator
Jun 14, 2011 at 7:22 PM

Then it's even easier, use the SingleUserAuthorizer, which I recently added, like this:

            // configure the OAuth object
            var auth = new SingleUserAuthorizer
            {
                Credentials = new InMemoryCredentials
                {
                    ConsumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"],
                    ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"],
                    OAuthToken = ConfigurationManager.AppSettings["twitterOAuthToken"],
                    AccessToken = ConfigurationManager.AppSettings["twitterAccessToken"]
                }
            };

            // Remember, do not call authorize - you don't need it.
            // auth.Authorize();

            using (var twitterCtx = new TwitterContext(auth, "https://api.twitter.com/1/", "https://search.twitter.com/"))
            {
                //Log
                twitterCtx.Log = Console.Out;

                var users =
                    (from tweet in twitterCtx.User
                     where tweet.Type == UserType.Friends &&
                           tweet.ScreenName == "JoeMayo"
                     select tweet)
                    .ToList();

                users.ForEach(user =>
                {
                    var status =
                        user.Protected || user.Status == null ?
                            "Status Unavailable" :
                            user.Status.Text;

                    Console.WriteLine(
                        "ID: {0}, Name: {1}\nLast Tweet: {2}\n",
                        user.Identifier.UserID, user.Identifier.ScreenName, status);
                });
            }

You can get your OAuthToken and AccessToken from your Twitter Application page. Click on the My Access Token menu on the right.  Here's what each item means in LINQ to Twitter terms:

Twitter Access Token == LINQ to Twitter Credentials.OAuthToken

Twitter Access Secret == LINQ to Twitter Credentials.AccessToken

PinAuthorizer will work too, if you populate OAuthToken and AccessToken, but the Single User Authorizer is quicker and easier.

Joe

Jun 14, 2011 at 7:46 PM

Fantastic!!! Thank you!

I modified the PinAuthorizer to set the other properties and removed the URL stuff.

Where is SingleUserAuthorizer located in the code? I don't think it was included in the version which I downloaded today.

Also, I wanted to ask you if your LINQ implementation supports a generic Repository implementation approach. I noticed that the Search class has a commented out implentation of ITwitterEntity. I am assuming this interface was going to be used for this type of purpose?

Great job on this BTW. Very impressive!

Coordinator
Jun 14, 2011 at 8:21 PM

SingleUserAuthorizer is in the OAuth folder, it's relatively new.

I forgot to remove the ITwitterEntity code, but the best I can remember is that it was part of something I was experimenting with to make the code more extensible.  I think I finally settled on Raw queries.  Raw queries solve the problem where Twitter changes so often and includes new features before I can make the changes.  The Raw query lets you add strings and parameters, while still taking advantage of all LINQ to Twitter's extensibility and infrastructure, like compression, timings, OAuth, and more.

What were you thinking by "generic repository"? 

Twitter has a feature called Twitter Entities that contain certain types of Tweet metadata: http://dev.twitter.com/pages/tweet_entities.



Joe

Jun 14, 2011 at 8:50 PM

What I mean by creating a generic repository is creating an abstract class which implements the repository pattern and defines repository operations, such as Add, Delete, Read, and Update. This class would have two generic type parameters, one being a base class of the Entity and another being the type of the context. The base class would then define how to implent each operation and completely encapsulate these operations. Child classes would be implmeneted for each entity and they would be only responsible for implementing Read operations on each entity such as Fetch Single, Fetch Single by Search, Fetch All, etc.

Entity Framework 4 is a good example of this.

I've implemented a repository for EF4 and here's the approach I took:

  • Created base abstract class which defined core operations and called them Save, Delete. This base class encapsulates all core EF4 operations on how to manipulate the object context
  • The base abstract class has two type parameters: TEntityObject, which is of type EntityObject and TContext which is of type ObjectContext. I have a property which
  • An instance of the ObjectContext is provided in a constructor.
  • Save Operation - Can be one of two things: Add, Update (I have defined base operations for each and then by determining the entity object's state, I can determine if it's Add or Update)
    • Add - Using the ObjectContext.AddObject method which takes a type of TEntityObject which adds it to the context.
    • Update - Entity Framework automatically handles this
    • ObjectContext.SaveChanges() is called after this and this pushes the changes
    • Delete - ObjectContext.DeleteObject takes in an entityobject and deletes it from the context
  • Fetch Data - These are to be implented in the child repository classes for each entity. Such operations include:
    • Retrieve Single
    • Retrieve Single(search value)
    • Retrieve Multiple(search value)
    • Retrieve All
    • Others which are defined
    • LINQ is used

 

Jun 14, 2011 at 10:04 PM

One other question Joe:

I am attempting to return tweets since a particular status id. Here's the code I'm using. For some reason when I specify the status Id I am not getting any results back. Any suggestions? Thanks!!

           var statusTweets = from tweet in context.Status
                               where tweet.Type == StatusType.User
                                     && tweet.SinceID == Convert.ToUInt64(tweetId)
                               select tweet;
Coordinator
Jun 15, 2011 at 2:28 PM
Edited Jun 15, 2011 at 2:28 PM

It might be because your account only had retweets since tweetID.  To get retweets, set the IncludeRetweets filter to true.  Here's an example:

            var statusTweets =
                from tweet in twitterCtx.Status
                where tweet.Type == StatusType.User &&
                      tweet.ScreenName == "JoeMayo" &&
                      tweet.SinceID == 79608230359212032 &&
                      tweet.IncludeRetweets == true
                select tweet;

Joe

Jun 16, 2011 at 3:23 PM

Thanks. That worked. I noticed whenever I included the screen name as well, it pulled the tweets since the tweet specified.

Jun 20, 2011 at 11:27 PM

Hi,

Unfortunately that does not work for me. I have the following code:

        [Fact]
        public void RegisterApplication()
        {
            var auth = new PinAuthorizer()
            {
                Credentials = new InMemoryCredentials
                {
                    ConsumerKey = consumerKey,
                    ConsumerSecret = consumerSecret,
                    OAuthToken = oAuthToken,
                    AccessToken = accessToken
                }
            }; 
            var twitterContext = new TwitterContext(auth){Log = Console.Out};
            var queryable = from s in twitterContext.Status where s.Type == StatusType.Public select s.Text;

            queryable.ToList().ForEach(Console.WriteLine);
        }

 

when I run it I get that:

------ Test started: Assembly: Tests.dll ------

Test 'Tests.AdhocTests.RegisterApplication' failed: System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
---- System.ArgumentNullException : Value cannot be null.
Parameter name: str
	at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
	at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
	at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
	at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
	at LinqToTwitter.TwitterQueryProvider.Execute[TResult](Expression expression)
	at LinqToTwitter.TwitterQueryable`1.GetEnumerator()
	at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
	at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
	D:\Work\WpfSimpleTweitterClone\Tests\Class1.cs(61,0): at Tests.AdhocTests.RegisterApplication()
	----- Inner Stack Trace -----
	at System.Security.Permissions.FileIOPermission.HasIllegalCharacters(String[] str)
	at System.Security.Permissions.FileIOPermission.AddPathList(FileIOPermissionAccess access, AccessControlActions control, String[] pathListOrig, Boolean checkForDuplicates, Boolean needFullPath, Boolean copyPathList)
	at System.Security.Permissions.FileIOPermission..ctor(FileIOPermissionAccess access, String path)
	at System.Configuration.UriSectionReader.GetSectionData()
	at System.Configuration.UriSectionReader.Read(String configFilePath, UriSectionData parentData)
	at System.Configuration.UriSectionInternal.LoadUsingCustomParser(String appConfigFilePath)
	at System.Configuration.UriSectionInternal.GetSection()
	at System.Uri.InitializeUriConfig()
	at System.Uri.InitializeUri(ParsingError err, UriKind uriKind, UriFormatException& e)
	at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
	at System.Uri..ctor(String uriString)
	at System.Net.WebRequest.Create(String requestUriString)
	at LinqToTwitter.OAuthAuthorizer.Get(String url)
	at LinqToTwitter.TwitterExecute.QueryTwitter[T](String url, IRequestProcessor`1 reqProc)
	at LinqToTwitter.TwitterContext.Execute[T](Expression expression, Boolean isEnumerable)

Output from Tests.AdhocTests.RegisterApplication:
  --Log Starts Here--
  Query:https://api.twitter.com/1/statuses/public_timeline.xml
  Method:QueryTwitter
  --Log Ends Here--

0 passed, 1 failed, 0 skipped, took 1.14 seconds (xunit).

As I can see something is getting wrong with some URL. Note that when context is created as new TwitterContext() everything works as expected.

Thanks in advance.

Coordinator
Jun 21, 2011 at 1:29 AM

the_joric,

Your code works for me and doesn't produce the same error.  Any chance you could pull up Fiddler and post the response.  If you do - double check to sanitize the output so it doesn't contain any of your keys.

Joe

Jul 7, 2011 at 9:01 PM

Your last code/dll update was March could you put up the new code?

Coordinator
Jul 7, 2011 at 9:11 PM

Hi steveplasko,

Agree, it's been a while and I need to do another release soon.  We made some rather huge changes in the code base this cycle and I'm making sure it passes a quality bar.  I have one more issue to resolve and will begin working on the next release.

Joe