Creating an ASP.NET RSS Blog Roller with C#
We needed an RSS blog roller for http://www.interfacett.com so we could show employee blogs from various sites. While there are several out there for .NET we wanted something that we could build on and customize so Spike Xavier and I put together a quick class named RssBlogRoller that would read an array of blog URLs, grab a specific number of items and then sort them based upon the <pubDate> element. It was a fun little exercise that will be useful in classes as well since it demonstrates generics, XmlReaders (and sub readers) and the ASP.NET ObjectDataSource.
The code for the RssBlogRoller is shown below. I've refactored it several times now and while it's not perfect, it gets the job done. RssBlogRoller has a method named GetRssItems that returns an array of RssItem objects (also shown below).
using System.Web.Configuration;
using System.Xml;
using System.IO;
using System.Collections.Generic;
using System.Web;
namespace InterfaceTT {
public class RSSBlogRoller {
public static RSSItem[] GetRssItems(string rssURLs,
int numberToShow)
{
//Try to grab URLs from web.config
if (rssURLs == null) rssURLs =
WebConfigurationManager.AppSettings["BlogUrls"];
if (String.IsNullOrEmpty(rssURLs))
{
HttpContext.Current.Trace.Warn("GetRssItems",
"rssURLs is empty. No blogs to parse were found.");
return null;
}
//Get each person's blog URL
string[] rssUrlsArray = rssURLs.Split(new char[] { ';' });
//Check if we have any URLs to retrieve
if (rssUrlsArray == null || rssUrlsArray.Length == 0)
{
HttpContext.Current.Trace.Warn("GetRssItems",
"rssURLs is empty. No blogs to parse were found.");
return null;
}
//Create dictionary to store each person's blog entries
//Key is blog URL, value is all the person's entries
Dictionary<string, List<RSSItem>> namesFeeds =
new Dictionary<string, List<RSSItem>>();
foreach (string url in rssUrlsArray)
{
if (url.Length > 0)
{
List<RSSItem> blogItems = new List<RSSItem>();
RSSItem currItem = null;
XmlReader reader = null;
try
{
reader = XmlReader.Create(url);
while (reader.Read())
{
//Exit read operation if we've
//found all we need
if (blogItems.Count > numberToShow) break;
if (reader.NodeType == XmlNodeType.Element
&& reader.Name.ToLower() == "item")
{
//Create a sub XmlReader
currItem = ReadRSSItem(reader.ReadSubtree());
}
//After each item end tag create HTML output
if (reader.NodeType == XmlNodeType.EndElement
&& reader.Name.ToLower() == "item")
{
if (currItem != null)
blogItems.Add(currItem);
}
}
}
catch (Exception exp)
{
HttpContext.Current.Trace.Warn("GetRssItems",
"Error in reading RSS feed: " + exp.Message);
}
finally
{
if (reader != null) reader.Close();
}
if (blogItems.Count > 0)
{
namesFeeds.Add(url, blogItems);
}
}
} //End For
RSSItem[] newItems = GetRSSItems(namesFeeds, numberToShow);
//Will sort by pubDate...implements IComparable<T>
Array.Sort(newItems);
return newItems;
}
private static RSSItem ReadRSSItem(XmlReader subReader)
{
string title = null;
string link = null;
string desc = null;
DateTime? pubDate = null; //Nullable<DateTime>
string authorName = null;
subReader.MoveToContent();
while (subReader.Read())
{
switch (subReader.Name.ToLower())
{
case "title":
title = subReader.ReadString();
break;
case "link":
link = subReader.ReadString();
break;
case "description":
desc = subReader.ReadString();
break;
case "pubdate":
string pubDateVal = subReader.ReadString();
DateTime d;
if (DateTime.TryParse(pubDateVal, out d))
{
pubDate = Convert.ToDateTime(pubDateVal);
}
break;
case "author":
authorName = subReader.ReadString();
break;
}
}
if (!String.IsNullOrEmpty(title)
&& !String.IsNullOrEmpty(link)
&& !String.IsNullOrEmpty(desc))
{
RSSItem item = new RSSItem(title, link,
desc, pubDate, authorName);
return item;
}
return null;
}
private static RSSItem[] GetRSSItems(Dictionary<string,
List<RSSItem>> namesFeeds, int numberToShow)
{
if (numberToShow < 1) numberToShow = 1;
List<RSSItem> blogItems = new List<RSSItem>();
int i = 0;
if (namesFeeds != null && namesFeeds.Count > 0)
{
foreach (string key in namesFeeds.Keys)
{
List<RSSItem> items = namesFeeds[key];
if (items != null)
{
int count = items.Count;
if (count > 0)
{
int j = 0;
while (j < numberToShow)
{
if (j == count) break;
blogItems.Add(items[j]);
j++;
i++;
}
}
else
{
namesFeeds.Remove(key);
}
}
}
}
else
{
HttpContext.Current.Trace.Warn("GetRSSItems",
"namesFeeds is null or has a 0 count.");
}
return blogItems.ToArray();
}
}
}
using System.Collections.Generic;
namespace InterfaceTT {
public class RSSItem : IComparable<RSSItem> {
private string _Title;
private string _Link;
private string _Description;
private DateTime? _PubDate;
private string _AuthorName;
public RSSItem(string title,string link,string desc,
DateTime? pubDate,string authorName) {
_Title = title;
_Link = link;
_Description = desc;
_PubDate = pubDate;
_AuthorName = authorName;
}
public string Title {
get {
return _Title;
}
set {
_Title = value;
}
}
public string Link {
get {
return _Link;
}
set {
_Link = value;
}
}
public string Description {
get {
return _Description;
}
set {
_Description = value;
}
}
public DateTime? PubDate
{
get { return _PubDate; }
set { _PubDate = value; }
}
public string AuthorName
{
get { return _AuthorName; }
set { _AuthorName = value; }
}
#region IComparable<RSSItem> Members
public int CompareTo(RSSItem other)
{
if (this.PubDate < other.PubDate) return 1;
if (this.PubDate > other.PubDate) return -1;
return 0; //The are equal
}
#endregion
}
}
The user control that displays the blog data for employees is shown next. It uses the ObjectDataSource control to bind to the RssBlogRoller class.
<%@ Control Language="C#" AutoEventWireup="true"
CodeFile="BlogRoller.ascx.cs" Inherits="UserControl_BlogRoller" %>
<%@ OutputCache Duration="900" VaryByParam="None" %>
<asp:DataList ID="DataList1" runat="server"
DataSourceID="odsRss" HorizontalAlign="Left" RepeatLayout="Flow">
<ItemTemplate>
<a href='<%# Eval("Link") %>'><%# Eval("Title") %></a>
<br />
<%# Eval("PubDate") %> By <%# Eval("AuthorName") %>
<br />
<%# Strip(Eval("Description"),Eval("Link")) %>
</li>
</ItemTemplate>
</asp:DataList>
<asp:ObjectDataSource ID="odsRss" runat="server"
SelectMethod="GetRssItems" TypeName="InterfaceTT.RSSBlogRoller">
<SelectParameters>
<asp:Parameter DefaultValue=""
Name="rssURLs" Type="String" />
<asp:Parameter DefaultValue="1"
Name="numberToShow" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
An example of the blog roller in action can be found at http://www.interfacett.com/ and the code can be downloaded here.