A custom Twitter service is used for all communication with the Twitter web API. The service also provides the capability to cache the results of calls to Twitter using a local database.
The viewmodel, which presents the Twitter timeline information, consumes the Twitter service using an ITwitterService
interface.
The ITwitterService
contains two methods designed to execute asynchronously. They are as follows:
GetTimeline(
string screenName,
Action
<ResultEventArgs
<TwitterUser>> completeAction)
GetCachedTimeline(
string screenName,
Action
<ResultEventArgs
<TwitterUser>> completeAction)
A caller of either method provides an Action
argument, which, on completion of the method, is invoked, notifying the caller of the result.
The ResultEventArgs
class either contains the result produced by the operation or an Exception
object. ResultEventArgs
is presented in Chapter 27, “Communicating with Network Services.”
GetTimeline
uses the Twitter web API to retrieve a Twitter user’s details and list of status updates. After a response is received from the Web, the result is converted into entity objects representing the Twitter user and the status updates (TimelineItem
objects).
GetCachedTimeline
is used to retrieve a TwitterUser
from the database. This method does not produce a result until GetTimeline
has successfully completed at least once, because until then no data has been placed in the database.
The default implementation of the ITwitterService
is the TwitterService
class. TwitterService
relies on the TwitterDatabaseUtility
. A TwitterDataContext
is created when the TwitterService
is instantiated; as shown:
public class TwitterService : ITwitterService
{
readonly TwitterDatabaseUtility twitterDatabaseUtility
= new TwitterDatabaseUtility();
readonly TwitterDataContext dataContext;
public TwitterService()
{
dataContext = twitterDatabaseUtility.CreateContext();
}
...
}
The public GetTimeline
method queues the Twitter call on a ThreadPool
thread. To demonstrate population using the local database, the ThreadPool
thread is made to wait for 3 seconds before calling the main GetTimelineCore
method. If an exception is raised, the complete action is called, as shown:
public void GetTimeline(
string screenName, Action<ResultEventArgs<TwitterUser>> completeAction)
{
ArgumentValidator.AssertNotNullOrWhiteSpace(screenName, "userName");
ArgumentValidator.AssertNotNull(completeAction, "completeAction");
ThreadPool.QueueUserWorkItem(
delegate
{
try
{
/*Wait for a moment to demonstrate the database retrieval.*/
Wait(3000); /* Equivalent to Thread.Sleep(3000); */
GetTimelineCore(screenName, completeAction);
}
catch (Exception ex)
{
Console.WriteLine(ex);
completeAction(new ResultEventArgs<TwitterUser>(null, ex));
}
});
}
GetTimelineCore
uses the Twitter web API (see Listing 29.4). A WebClient
is used to dispatch an HTTP request to the site. When the call returns, it is handled within a lambda expression. The result of the call is an XML fragment, which we convert to an XElement
using the XElement.Parse
method.
The user information is then extracted from the root element, before building a list of TimelineItems
. Finally, the data context is used to store the TwitterUser
and its associated TimelineItems
in the database.
void GetTimelineCore(
string screenName, Action<ResultEventArgs<TwitterUser>> completeAction)
{
WebClient twitterClient = new WebClient();
twitterClient.DownloadStringCompleted
+= (o, args) =>
{
if (args.Error != null)
{
completeAction(
new ResultEventArgs<TwitterUser>(null, args.Error));
return;
}
try
{
XElement rootElement = XElement.Parse(args.Result);
XElement element = rootElement.Descendants("status").First();
XElement userElement = element.Descendants("user").First();
var twitterUser = new TwitterUser
{
Id = userElement.Element("id").Value,
ScreenName = userElement.Element("screen_name").Value,
Description = userElement.Element("description").Value,
ImageUrl = userElement.Element("profile_image_url").Value,
};
IEnumerable<TimelineItem> items = from statusElement
in rootElement.Descendants("status")
select new TimelineItem
{
Id = statusElement.Element("id").Value,
ReceivedTime = ConvertToDateTime(
statusElement.Element("created_at").Value),
Text = statusElement.Element("text").Value
};
TwitterUser storedUser = dataContext.TwitterUsers.Where(
user => user.ScreenName == screenName).FirstOrDefault();
if (storedUser != null)
{
dataContext.TimelineItems.DeleteAllOnSubmit(
dataContext.TimelineItems.Where(
item => item.TwitterUserId == storedUser.Id));
dataContext.TwitterUsers.DeleteOnSubmit(storedUser);
dataContext.SubmitChanges();
}
twitterUser.TimelineItems.AddRange(items);
dataContext.TwitterUsers.InsertOnSubmit(twitterUser);
dataContext.SubmitChanges();
completeAction(new ResultEventArgs<TwitterUser>(twitterUser));
}
catch (Exception ex)
{
Debug.WriteLine("Unable to get timeline. " + ex);
completeAction(new ResultEventArgs<TwitterUser>(null, ex));
return;
}
};
twitterClient.DownloadStringAsync(new Uri(
"http://api.twitter.com/1/statuses/user_timeline.xml?screen_name="
+ screenName));
}
After the Twitter web API has been queried and the result stored in the database, the cached data can be retrieved from the database. This allows the Twitter timeline information to remain viewable when no network connection is available.
Note
Changes to the entity model are not persisted to the database until DataContext.SubmitChanges
is called. New entity objects that have been added to the data context are not inserted into the database until SubmitChanges
is called, and entities that have been removed from the data context are not deleted from the database until SubmitChanges
is called.
Retrieving the cached data from the local database is the task of the TwitterService.GetCachedTimeline
method.
You see the same pattern applied as earlier; the principle GetCachedTimelineCore
method is called from a ThreadPool
thread, as shown:
public void GetCachedTimeline(
string screenName, Action<ResultEventArgs<TwitterUser>> completeAction)
{
ArgumentValidator.AssertNotNullOrWhiteSpace(screenName, "userName");
ArgumentValidator.AssertNotNull(completeAction, "completeAction");
ThreadPool.QueueUserWorkItem(
delegate
{
try
{
GetCachedTimelineCore(screenName, completeAction);
}
catch (Exception ex)
{
Console.WriteLine(ex);
completeAction(new ResultEventArgs<TwitterUser>(null, ex));
}
});
}
The GetCachedTimelineCore
method uses the data context to retrieve the Twitter user with the specified screen name, shown in the following excerpt:
void GetCachedTimelineCore(
string userName, Action<ResultEventArgs<TwitterUser>> completeAction)
{
TwitterUser storedUser = dataContext.TwitterUsers.Where(
user => user.ScreenName == userName).FirstOrDefault();
completeAction(new ResultEventArgs<TwitterUser>(storedUser));
}
You saw how the TwitterService
is able to retrieve live data from the Twitter web API and then cache it using a local database. Subsequent sections look at presenting this data to the user.
3.16.79.65