For the Facebook Silverlight 2 Beta 2 application I'm writing, I need to be able to process a number of RSS feeds. In Silverlight 2 Beta 2, processing RSS is, well, really simple :)
Here's a class and associated event and exception classes I wrote to handle downloading the feed, and loading it into the RSS objects:
#region Support Classes (exception, eventargs)
public class FeedException : Exception
{
public FeedException()
: base("Exception retrieving or processing feed.")
{
}
public FeedException(string message)
: base("Exception retrieving or processing feed. " + message)
{
}
public FeedException(string message, Exception innerException)
: base("Exception retrieving or processing feed. " + message,
innerException)
{
}
}
public class FeedRetrievedEventArgs : EventArgs
{
private SyndicationFeed _feed;
public FeedRetrievedEventArgs(SyndicationFeed feed)
{
_feed = feed;
}
public SyndicationFeed Feed
{
get { return _feed; }
}
}
public class FeedErrorEventArgs : EventArgs
{
private Exception _exception;
public FeedErrorEventArgs(Exception exception)
{
_exception = exception;
}
public Exception Error
{
get { return _exception; }
}
}
#endregion
public class RssFeedGrabber
{
public event EventHandler<FeedRetrievedEventArgs> FeedRetrieved;
public event EventHandler<FeedErrorEventArgs> FeedError;
private const string _nullResultErrorMessage =
"The returned stream was null. "+
"The site may not have a valid client access policy in place.";
private const string _parseErrorMessage =
"Unable to parse feed.";
/// <summary>
/// Makes an async call to return the feed data
/// </summary>
/// <param name="url">URL of the feed</param>
public void RetrieveFeedAsync(Uri url)
{
WebClient client = new WebClient();
client.OpenReadCompleted +=
new OpenReadCompletedEventHandler(client_OpenReadCompleted);
client.Encoding = System.Text.Encoding.UTF8;
client.OpenReadAsync(url);
}
/// <summary>
/// Event handler called when the WebClient returns the data from the call
/// </summary>
private void client_OpenReadCompleted(object sender,
OpenReadCompletedEventArgs e)
{
// process the feed
if (e.Error == null && e.Result != null)
{
using (XmlReader reader = XmlReader.Create(e.Result))
{
try
{
SyndicationFeed feed = SyndicationFeed.Load(reader);
FeedRetrievedEventArgs args =
new FeedRetrievedEventArgs(feed);
OnFeedRetrieved(args);
}
catch (Exception ex)
{
// the loader couldn't load the feed
FeedErrorEventArgs args =
new FeedErrorEventArgs(
new FeedException(_parseErrorMessage, ex));
OnFeedError(args);
}
}
}
else
{
if (e.Error != null)
{
// there was an error returned from the call
FeedErrorEventArgs args = new FeedErrorEventArgs(e.Error);
OnFeedError(args);
}
else
{
// an empty stream. You get this silent failure when there
// is no crossdomain file in place (at least in beta2)
FeedErrorEventArgs args =
new FeedErrorEventArgs(
new FeedException(_nullResultErrorMessage));
OnFeedError(args);
}
}
}
/// <summary>
/// Internal method used to raise the feed event
/// </summary>
protected void OnFeedRetrieved(FeedRetrievedEventArgs args)
{
if (FeedRetrieved != null)
FeedRetrieved(this, args);
}
/// <summary>
/// Internal method used to raise the error event
/// </summary>
protected void OnFeedError(FeedErrorEventArgs args)
{
if (FeedError != null)
FeedError(this, args);
}
}
The class simply makes an async web call to retrieve the feed, then raises an event with the parsed feed. If you extend the feed formatter classes, you'll want to modify this code to use the appropriate one. If I end up extending anything, I'll write (and post) a templated version of this class.
Make sure you add a reference to System.ServiceModel.Syndication as well as add the appropriate using statement:
using System.ServiceModel.Syndication;
Keep in mind that the site hosting the rss feed will need to have an appropriate cross domain file in place, or you'll need to use a workaround like the ones described in Tim Heuer's blog post.
To use this class, simply call it from your code like this:
void Page_Loaded(object sender, RoutedEventArgs e)
{
RssFeedGrabber grabber = new RssFeedGrabber();
grabber.FeedRetrieved +=
new EventHandler<FeedRetrievedEventArgs>(grabber_FeedRetrieved);
grabber.FeedError +=
new EventHandler<FeedErrorEventArgs>(grabber_FeedError);
grabber.RetrieveFeedAsync(
new Uri("http://feeds.feedburner.com/PeteBrown"));
}
void grabber_FeedError(object sender, FeedErrorEventArgs e)
{
// there was an error with the feed. display appropriate
// message or take action here
}
void grabber_FeedRetrieved(object sender, FeedRetrievedEventArgs e)
{
foreach (SyndicationItem item in e.Feed.Items)
{
System.Diagnostics.Debug.WriteLine(item.Title.Text);
}
}
(as an aside, I don't usually break up my code lines as seen above, unless the lines are extremely long; I broke them up just so they fit in this post.)
Let me know if you have any questions or run into any issues, or if you've come up with an even simpler/cooler/more functional version of this class.