Introduction
Since a few weeks, I've become more and more a fan of Google Reader. This great online RSS reader supports starring items, extended search, ... anything you expect from a good RSS reader. What I would like to do in the following articles is connect to and consume Google services.
Before starting, I do need to point out that there is a .NET ready API written by Google, called the Google Data Protocol. This library supports many of the Google services and is very well documented.
You can find it at http://code.google.com/apis/gdata/.
Downsides for me personally:
- No support for the .NET Client Profile
- Reference to external assembly required
- No support/documentation for Google Reader?
It is also important to know that there are other .NET libraries available to consume Google Reader:
The problem with these libraries is that authentication does not work (anymore). Google changed the way authentication is done. More information can be found at http://groups.google.com/group/fougrapi/browse_thread/thread/e331f37f7f126c00
This solution:
- Works with ClientLogin authentication
- Using the .NET 4.0 Client Profile
GoogleSession
class answering all your needs
Authentication with ClientLogin
If you want to authenticate, a few things need to happen. A post made by xandy on StackOverFlow explains the process (points 1 to 3):
- Post to https://www.google.com/accounts/ClientLogin with login credentials.
- In return, three tokens will be passed if correct login: a. SID b. LSID c. Auth
- Save the Auth somewhere in application. Forget about SID and LSID (I guess they might remove them later on)
- In every request, add following in the header: headername:
Authorization value: GoogleLogin auth={Auth string} e.g. (in Java)
The following class does all this and returns the Auth token after a successful login:
public static class ClientLogin
{
private static string clientLoginUrl =
@"https://www.google.com/accounts/ClientLogin";
private static string postData =
@"service={0}&continue=http://www.google.com/&Email={1}&Passwd={2}&source={3}";
public static string GetAuthToken(
string service, string username, string password, string source)
{
string response = PostRequest(service, username, password, source);
string auth = ParseAuthToken(response);
return auth;
}
private static string ParseAuthToken(string response)
{
string auth = "";
try
{
auth = new Regex(@"Auth=(?<auth>\S+)").Match(response).Result("${auth}");
}
catch (Exception ex)
{
throw new AuthTokenException(ex.Message);
}
if (string.IsNullOrEmpty(auth))
{
throw new AuthTokenException("Missing or invalid 'Auth' token.");
}
return auth;
}
private static string PostRequest(
string service, string email, string password, string source)
{
ASCIIEncoding ascii = new ASCIIEncoding();
byte[] encodedPostData = ascii.GetBytes(
String.Format(postData, service, email, password, source));
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(clientLoginUrl);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = encodedPostData.Length;
using (Stream newStream = request.GetRequestStream())
newStream.Write(encodedPostData, 0, encodedPostData.Length);
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
HttpWebResponse faultResponse = ex.Response as HttpWebResponse;
if (faultResponse != null &&
faultResponse.StatusCode == HttpStatusCode.Forbidden)
throw new IncorrectUsernameOrPasswordException(
faultResponse.StatusCode, faultResponse.StatusDescription);
else
throw;
}
if (response.StatusCode != HttpStatusCode.OK)
throw new LoginFailedException(
response.StatusCode, response.StatusDescription);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
return reader.ReadToEnd();
}
}
That's it. If you invoke ClientLogin.GetAuthToken
, you'll get the auth token that can be used for each subsequent request.
You'll also get correctly typed exceptions depending on the error you get. If the exception is not known, it's just re-thrown.
The GoogleSession Class
Before we can use the Auth token to make the request, we need to take care of a few things:
- Reusing the Auth token
- Adding the Auth token to the request header (point 4 in the StackOverFlow post)
- Support for parameters (like top 10 items, etc.)
- Process the request in a reusable manner (
Response
as stream, string, feed, ...)
That's why we'll use the following class that encapsulates all these features:
public class GoogleSession : IDisposable
{
private string auth;
public GoogleSession(string auth)
{
this.auth = auth;
}
public WebResponse GetResponse(string url, params GoogleParameter[] parameters)
{
string formattedParameters = string.Empty;
foreach (var par in parameters)
formattedParameters += string.Format("{0}={1}&", par.Name, par.Value);
formattedParameters = formattedParameters.TrimEnd('&');
HttpWebRequest request = null;
if (formattedParameters.Length > 0)
request = (HttpWebRequest)WebRequest.Create(string.Format("{0}?{1}",
url, formattedParameters));
else
request = (HttpWebRequest)WebRequest.Create(url);
request.Headers.Add("Authorization", "GoogleLogin auth=" + auth);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
if (response == null)
throw new GoogleResponseNullException();
else if (response.StatusCode != HttpStatusCode.OK)
throw new GoogleResponseException(response.StatusCode,
response.StatusDescription);
return response;
}
public Stream GetResponseStream(string url, params GoogleParameter[] parameters)
{
return GetResponse(url, parameters).GetResponseStream();
}
public string GetSource(string url, params GoogleParameter[] parameters)
{
using (StreamReader reader = new StreamReader(
GetResponseStream(url, parameters)))
{
return reader.ReadToEnd();
}
}
public SyndicationFeed GetFeed(string url, params GoogleParameter[] parameters)
{
using (StreamReader reader = new StreamReader(
GetResponseStream(url, parameters)))
{
using (XmlReader xmlReader = XmlReader.Create(reader,
new XmlReaderSettings()))
{
return SyndicationFeed.Load(xmlReader);
}
}
}
public void Dispose()
{
auth = null;
}
}
What you can do with it:
- Create an authenticated response supporting parameters
- Get this response as a
Stream
- Get this response as a feed
- Get this response as a
string
(raw source)
Putting It All Together
Now that we can easily authenticate and make requests, let's make use of it. Here is an example of a command line application showing the last 5 articles from Google Reader.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("");
Console.Write(" Enter your Google username: ");
string username = Console.ReadLine();
Console.Write(" Enter your password: ");
string password = Console.ReadLine();
string auth = ClientLogin.GetAuthToken
("reader", username, password, "Sandworks.Google.App");
using (GoogleSession session = new GoogleSession(auth))
{
var feed = session.GetFeed
("http://www.google.com/reader/atom/user/-/state/com.google/reading-list</a />",
new GoogleParameter("n", "5"));
Console.WriteLine("");
Console.WriteLine(" Last 5 articles in Google Reader: ");
foreach (var item in feed.Items)
{
Console.WriteLine(" - " + item.Title.Text);
}
}
Console.ReadLine();
}
}
What it does:
- Asks for your Google account
- Asks for your password
- Authenticates using ClientLogin and gets the Auth token
- Creates a new GoogleSession using the Auth token
- Connects to Google Reader and gets the last 5 articles (using a
GoogleParameter
). - Displays those articles
And the result:
Don't worry about the Google Reader URL for now. In the next article, I'll talk about creating a class to talk to Google reader and how the URLs work.
Enjoy...
History
- 6th July, 2010: Initial post