Someone emailed me recently saying that they couldn’t find enough examples in .NET for talking to the recent proliferation of “Web 2.0 APIs” so I thought I’d put together a list and look at some source. I think that a nice API wrapper is usually a useful thing, but since these APIs are so transparent and basic, there's not really a huge need given LINQ to XML but I understand the knee-jerk reaction to hunt for a wrapper when faced with the word "API."
One thing to point out is that 99.9% of these APIs are calling
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
under the covers the doing something with the resulting string. Some hide the URL creation, some use XmlDocuments, others use XmlSerialization. When you use a random API you find on the net you're usually getting what you pay for. You're getting a one person's free view on how they perceived a certain API should be called. Some will like be more performant than others. Some might be better thought out than others.
I'll try to juxtapose a few differences between them, but I just want you to remember that we're talking about pushing Angle Brackets around, and little else. You can always do it yourself.
And so, Dear Reader, I present to you twenty-first in a infinite number of posts of "The Weekly Source Code."
Digg
Digg is a community-voted controlled explosion of news stories. Their API is "REST" and speaks XML or JSON on the wire.
DiggApiNET is a .NET Wrapper for the Digg API. It has no releases, so you'll have to get the source code. It was last updated in May of 2007. There's also another at CodeProject called, creatively, digg API.NET.
Let's talk philosophy of design and look a the first library. Here's some snippets pulled from all over the code. This API builds the URL and loads the results of the call into an XmlDocument, holds it for a second and SelectNodes the values into Digg-specific objects. These objects know about the existence of System.Xml.
01.
private
const
string
get_popular =
"http://services.digg.com/stories/popular/comments/{0}"
;
02.
03.
public
DiggComments GetPopular()
04.
{
05.
return
GetPopular(
new
Hashtable());
06.
}
07.
public
DiggComments GetPopular(Hashtable args)
08.
{
09.
string
uri = String.Format(get_popular, HttpBuildUrl(args));
10.
return
new
DiggComments(Request(uri));
11.
}
12.
public
DiggComments(XmlDocument xml_doc) :
base
(xml_doc,
"events"
)
13.
{
14.
_comments =
new
List<diggcomment>();
15.
if
(xml_doc.SelectSingleNode(
"events"
) ==
null
16.
|| xml_doc.SelectSingleNode(
"events"
).SelectNodes(
"comment"
) ==
null
) {
17.
throw
new
DiggApiException(
"XML response appears to be malformed, or contains unexpected data."
);
18.
}
19.
foreach
(XmlNode node
in
xml_doc.SelectSingleNode(
"events"
).SelectNodes(
"comment"
)) {
20.
_comments.Add(
new
DiggComment(node));
21.
}
22.
}</diggcomment>
This is a pretty straight-forward if not totally "clean" way to do it. SelectSingleNode and SelectNodes aren't too fast, but we're looking at tiny chunks of data, probably under 100k. I'd probably do it with either XmlReader or XmlSerializer, or more likely, LINQ to XML. I'd make a service that handle the wire protocol, and make the objects know less.
Facebook has a very sophisticated and deep API and there's lots of support for it on .NET that is well explained by Nikhil. You can develop for Facebook using the free Express Visual Studio editions.
There's quite a few available:
- There's Facebook.NET and Clarity Consulting's Facebook Developer Toolkit with detailed instructions.
- Jay Lagorio has a nice client library for Facebook written in VB.NET.
- JD Conley has released a Facebook library focused on Asynchrony called fbasync
Nikhil's Facebook client APIs feel well factored, with separate services for each major Facebook service and a FacebookSession object proving contextual state. Requests are pulled out into FacebookRequest and include asynchronous options, which is thoughtful.
Here's an edited (for brevity) example of a WinForm that allows you to set your Facebook status. I like the IsPermissionGranted call, which I think is clean and clever, given that there is a large enum of permissions.
01.
public
partial
class
StatusForm : Form {
02.
03.
private
const
string
_apiKey =
"[Your API Key]"
;
04.
private
const
string
_secret =
"[Your Secret]"
;
05.
06.
private
FacebookService _fbService;
07.
private
bool
_loggingIn;
08.
09.
private
void
LoadStatus() {
10.
_nameLabel.Text =
"Loading..."
;
11.
12.
User user = _fbService.Users.GetUser(
null
,
"name,status"
);
13.
if
(user !=
null
) {
14.
_nameLabel.Text = user.Name;
15.
16.
_statusTextBox.Text = user.Status.Message;
17.
_dateLabel.Text = user.Status.UpdateDate.ToLocalTime().ToString(
"g"
);
18.
}
19.
20.
bool
canSetStatus = _fbService.Permissions.IsPermissionGranted(Permission.SetStatus);
21.
_permissionsLink.Visible = !canSetStatus;
22.
_updateButton.Enabled = canSetStatus;
23.
_statusTextBox.ReadOnly = !canSetStatus;
24.
}
25.
26.
protected
override
void
OnActivated(EventArgs e) {
27.
base
.OnActivated(e);
28.
29.
if
((_fbService ==
null
) && (_loggingIn ==
false
)) {
30.
_loggingIn =
true
;
31.
32.
try
{
33.
FacebookClientSession fbSession =
new
FacebookClientSession(_apiKey, _secret);
34.
if
(fbSession.Initialize(
this
)) {
35.
_fbService =
new
FacebookService(fbSession);
36.
LoadStatus();
37.
}
38.
}
39.
finally
{
40.
_loggingIn =
false
;
41.
}
42.
}
43.
}
44.
45.
private
void
OnUpdateButtonClick(
object
sender, EventArgs e) {
46.
string
text = _statusTextBox.Text.Trim();
47.
48.
_fbService.Users.SetStatus(text,
/* includesVerb */
true
);
49.
LoadStatus();
50.
}
51.
}
52.
}
Interestingly, the Facebook API also includes it's own JsonReader and JsonWriter, rather than using the newJsonSerializer, presumably because the lib was written a year ago.
Windows Live Services
There's a bunch of info on http://dev.live.com/ and a bunch of complete sample apps with source as well as a Live SDK interactive site. The Live Contacts API, for example . Unfortunately with the Contact's API there's no .NET samples I can find that includes wrappers around the angle brackets, so you'll be parsing in whatever way you prefer.
The objects that are provided in the Alpha SDK are really focused initially on security and permissions. For example, before I was able to access my contacts programmatically, I had to explicitly allow access and chose a length of time to allow it. I allowed it for a day to be extra secure.
Once you've retrieved some data, it's very simple so a request likehttps://cumulus.services.live.com/wlddemo@hotmail.com/LiveContacts would give you:
01.
<
LiveContacts
>
02.
<
Owner
>
03.
<
FirstName
/>
04.
<
LastName
/>
05.
<
WindowsLiveID
/>
06.
</
Owner
>
07.
<
Contacts
>
08.
<
Contact
>
09.
<
ID
>{ContactID}</
ID
>
10.
<
WindowsLiveID
>{Passport Member Name}</
WindowsLiveID
>
11.
<
Comment
>comment here</
Comment
>
12.
<
Profiles
/>
13.
<
Emails
/>
14.
<
Phones
/>
15.
<
Locations
/>
16.
</
Contact
>
17.
</
Contacts
>
18.
</
LiveContacts
>
The Live Search API speaks SOAP and has samples in six languages including C#, VB, Ruby, PHP, Python, and Java.
YouTube
YouTube has two different versions of their API, but the original/old version is officially deprecated. Now that they are Google, the YouTube APIs are all GData style, replacing their REST/XML-RPC APIs.
There is a .NET Library that speaks the GData XML format and querying YouTube with C# is fairly simple from there. You can even upload videos programmatically to YouTube like this gentleman.
This fellow eschews GData's uber libraries and uses a StringBuilder to build the GData payload and that's OK. :)
01.
private
string
GetHeader(
string
title,
string
description, Catagory catagory,
02.
string
keywords,
string
videoFileName)
03.
{
04.
StringBuilder xml =
new
StringBuilder();
05.
xml.Append(boundary + lineTerm +
"Content-Type: application/atom+xml; charset=UTF-8"
+ lineTerm + lineTerm);
06.
xml.Append(
"<?xml version=/"1.0/"?><entry xmlns=/"http://www.w3.org/2005/Atom/" "
);
07.
xml.Append(
"xmlns:media=/"http://search.yahoo.com/mrss//" xmlns:yt=/"http://gdata.youtube.com/schemas/2007/">"
);
08.
xml.AppendFormat(
"<media:group><media:title type=/"plain/">{0}</media:title>"
, title);
09.
xml.AppendFormat(
"<media:description type=/"plain/">{0}</media:description>"
, description);
10.
xml.AppendFormat(
"<media:category scheme=/"http://gdata.youtube.com/schemas/2007/categories.cat/">{0}</media:category>"
, catagory);
11.
xml.AppendFormat(
"<media:keywords>{0}</media:keywords>"
, keywords);
12.
xml.Append(
"</media:group></entry>"
+ lineTerm);
13.
xml.Append(boundary + lineTerm +
"Content-Type: video/*"
+ lineTerm +
"Content-Transfer-Encoding: binary"
+ lineTerm + lineTerm);
14.
return
xml.ToString();
15.
}
GData
GData is Google's standard protocol for moving data around via XML and HTTP. There are GData endpoints for Blogger, Google Calendar, Notebook, Spreadsheets, Documents, Picassa, etc. From their site:
NET Developer Guides exist for specific Data APIs. They can be found under the page for each Data API
The GData C# client is written by Google, so I was really interested to read their code as their interview process is legendary and I assume everyone is a 17 year old PhD. The code is exceedingly object oriented with more than 165 files over 10 folders (not counting unit tests and project stuff). It's also VERY well commented, but interestingly, not always commented using the standard XML comments most MSFT Programmers use, but rather a different format I'm not familiar with.
All the APIs are fairly similar. Here's a GData sample that Queries the Calendar for events within a date range.
01.
static
void
DateRangeQuery(CalendarService service, DateTime startTime, DateTime endTime)
02.
{
03.
EventQuery myQuery =
new
EventQuery(feedUri);
04.
myQuery.StartTime = startTime;
05.
myQuery.EndTime = endTime;
06.
07.
EventFeed myResultsFeed = service.Query(myQuery)
as
EventFeed;
08.
09.
Console.WriteLine(
"Matching events from {0} to {1}:"
,
10.
startTime.ToShortDateString(),
11.
endTime.ToShortDateString());
12.
Console.WriteLine();
13.
for
(
int
i = 0; i < myResultsFeed.Entries.Count; i++)
14.
{
15.
Console.WriteLine(myResultsFeed.Entries[i].Title.Text);
16.
}
17.
Console.WriteLine();
18.
}
Here's an example that downloads all the pictures from a specific username in Picassa using C#. Everything in GData is an "AtomEntry" and many have extensions. You can handle the GData types or use specific sub-classes like PhotoQuery, or whatever, to make thing easier.
01.
private
static
void
DownAlbum(
string
UserN,
string
AlbumN)
02.
{
03.
string
fileName;
04.
Uri uriPath;
05.
WebClient HttpClient =
new
WebClient();
06.
// Three important elements of PicasaWeb API are
07.
// PhotoQuery, PicasaService and PicasaFeed
08.
PhotoQuery query =
new
PhotoQuery();
09.
query.Uri =
new
Uri(PhotoQuery.CreatePicasaUri(UserN, AlbumN));
10.
PicasaService service =
new
PicasaService(
"Sams PicasaWeb Explorer"
);
11.
PicasaFeed feed = (PicasaFeed)service.Query(query);
12.
13.
Directory.SetCurrentDirectory(
"c://"
);
14.
foreach
(AtomEntry aentry
in
feed.Entries)
15.
{
16.
uriPath =
new
Uri(aentry.Content.Src.ToString());
17.
fileName = uriPic.LocalPath.Substring(uriPath.LocalPath.LastIndexOf(
'/'
)+1);
18.
try
{
19.
Console.WriteLine(
"Downloading: "
+ fileName);
20.
HttpClient.DownloadFile(aentry.Content.Src.ToString(), fileName);
21.
Console.WriteLine(
"Download Complete"
);
22.
}
23.
catch
(WebException we)
24.
{ Console.WriteLine(we.Message); }
25.
}
26.
}
You can also certainly use any standard System.Xml APIs if you like.
GData is an extension of the Atom Pub protocol. Atom Pub is used by Astoria (ADO.NET Data Extensions) which can be accessed basically via "LINQ to REST."
Flickr
Flickr has a nice API and WackyLabs has a CodePlex project for their FlickrNET API Library written in C#. It's also confirmed to work on Compact Framework and Mono as well as .NET 1.1 and up. There's a fine Coding4Fun article on this library.
This API couldn't be much easier to use. For example, this searches for photos tagged blue and sky and makes sure it returns the DateTaken and OriginalFormat.
1.
PhotosSearchOptions options =
new
PhotosSearchOptions();
2.
options.Tags =
"blue,sky"
;
3.
options.Extras |= PhotoSearchExtras.DateTaken | PhotoSearchExtras.OriginalFormat;
4.
Photos photos = flickr.PhotosSearch(options);
The PhotosSearch() method includes dozens of overloads taking date ranges, paging and other options. All the real work happens in GetResponse() via GetResponseCache(). The URL is built all in one method, the response is retrieved and deserialized via XmlSerializer. This API is the closest to the way I'd do it. It's pragmatic, uses as much of the underlying libraries as possible. It's not really extensible or overly OO, but it gets the job done cleanly.
Since Flickr is a data intensive thing, this library also includes a thread safe PersisitentCache for storing all that data. I'd probably just have used System.Web.Cache because it can live in any application, even ones outside ASP.NET. However, theirs is a Persistent one, saving huge chunks of data to a configurable location. It's actually an interesting enough class that it could be used outside of this lib, methinks. It stores everything in a super "poor man's database," basically a serialized Hashtable of blobs, ala (gasp) OLE Structured Storage.
WordPress and XML-RPC based Blogs
Most blogs use either the Blogger or MetaWeblog APIs and they are easy to call with .NET. That includes MSN Spaces, DasBlog, SubText, etc. There's samples deep on MSDN on how to call XML-RPC with C# or VB.
Windows Live Writer and BlogJet use these APIs to talk to blogs when you're authoring a post, so I'm using .NET and XML-RPC right now. ;)
A very simple example in VB.NET using the very awesome XML-RPC.NET library is here. Here's a more complete example and here's a mini blogging client.
DasBlog uses this library to be an XML-RPC Server.
In this sample, the type "IWP" derives from XmlRpcProxy and uses the category structure. The library handles all the mappings an deserializaiton such that calling XML-RPC feels ;like using any Web Service, even though XML-RPC is a precursor to SOAP and not the SOAP you're used it.
1.
Dim proxy As IWP = XmlRpcProxyGen.Create(Of IWP)()
2.
Dim args() As String = {“http:
//myblog.blogstogo.com”, _
3.
“username”, “password”}
4.
Dim categories() As category
5.
categories = proxy.getCategories(args)
You can also use WCF to talk XML-RPC
I've talked about Twitter before and they have a Twitter API that is at least an order of magnitude more important than their site. There is a pile of source out there to talk to Twitter.
Last year Alan Le blogged about his adventures in creating a library around Twitter's API and Witty is aactively developed WPF C# application that fronts Twitter. You can browse their source and see their simple TwitterLib.
TwitterNet.cs is the meat of it and just builds up objects using XmlDocuments and does what I called "left hand/right hand" code. That's where you've got an object on the left and some other object/bag/pileOdata on the right and you spend a lot of lines just going "left side, right side, left side, right side.
For (trimmed) example:
01.
public
UserCollection GetFriends(
int
userId)
02.
{
03.
UserCollection users =
new
UserCollection();
04.
05.
// Twitter expects http://twitter.com/statuses/friends/12345.xml
06.
string
requestURL = FriendsUrl +
"/"
+ userId + Format;
07.
08.
int
friendsCount = 0;
09.
10.
// Since the API docs state "Returns up to 100 of the authenticating user's friends", we need
11.
// to use the page param and to fetch ALL of the users friends. We can find out how many pages
12.
// we need by dividing the # of friends by 100 and rounding any remainder up.
13.
// merging the responses from each request may be tricky.
14.
if
(currentLoggedInUser !=
null
&& currentLoggedInUser.Id == userId)
15.
{
16.
friendsCount = CurrentlyLoggedInUser.FollowingCount;
17.
}
18.
else
19.
{
20.
// need to make an extra call to twitter
21.
User user = GetUser(userId);
22.
friendsCount = user.FollowingCount;
23.
}
24.
25.
int
numberOfPagesToFetch = (friendsCount / 100) + 1;
26.
27.
string
pageRequestUrl = requestURL;
28.
29.
for
(
int
count = 1; count <= numberOfPagesToFetch; count++)
30.
{
31.
pageRequestUrl = requestURL +
"?page="
+ count;
32.
HttpWebRequest request = WebRequest.Create(pageRequestUrl)
as
HttpWebRequest;
33.
request.Credentials =
new
NetworkCredential(username, password);
34.
35.
try
36.
{
37.
using
(HttpWebResponse response = request.GetResponse()
as
HttpWebResponse)
38.
{
39.
StreamReader reader =
new
StreamReader(response.GetResponseStream());
40.
XmlDocument doc =
new
XmlDocument();
41.
doc.Load(reader);
42.
XmlNodeList nodes = doc.SelectNodes(
"/users/user"
);
43.
44.
foreach
(XmlNode node
in
nodes)
45.
{
46.
User user =
new
User();
47.
user.Id =
int
.Parse(node.SelectSingleNode(
"id"
).InnerText);
48.
user.Name = node.SelectSingleNode(
"name"
).InnerText;
49.
user.ScreenName = node.SelectSingleNode(
"screen_name"
).InnerText;
50.
user.ImageUrl = node.SelectSingleNode(
"profile_image_url"
).InnerText;
51.
user.SiteUrl = node.SelectSingleNode(
"url"
).InnerText;
52.
user.Location = node.SelectSingleNode(
"location"
).InnerText;
53.
user.Description = node.SelectSingleNode(
"description"
).InnerText;
54.
55.
users.Add(user);
56.
}
57.
58.
}
59.
}
60.
catch
(WebException webExcp)
61.
{
62.
// SNIPPED BY SCOTT
63.
}
64.
}
65.
return
users;
66.
}
So far, there's a .NET lib for every Web 2.0 application I've wanted to use. I even banged a .NET Client out forWesabe last year then did it again in IronRuby.
Enjoy. Which (of the hundreds) did I miss?