Build an RSS DataList Control in ASP.NET

RSS is finally getting the recognition it deserves. SitePoint now publishes RSS, and large news agencies like the BBC, the New York Times and CNN also publish RSS feeds. Now, developers can integrate content from a wide range of producers within their own applications, giving users a greater incentive to return, and opening up new possibilities for application development.

This article will show you how to create a server control based on the DataList to provide a self-contained and manageable way of consuming RSS feeds within your ASP.NET applications.

RSS, RDF, Atom�What’s the Situation?

SitePoint’s Nathan Matias has written a Get Off Your RSS – A Quick Introduction to RSS, though there is more to know before we can start developing applications to consume RSS feeds.

First, a quick history lesson. Think back to the late 90s. The Internet had boomed, stock options were looking rosy, imaginations were firing, and "push technology" was being touted as the next leap forward. Companies like Microsoft and Netscape both integrated push clients into their browser software. Start-ups like PointCase and Marimba were selling the virtues of having information pushed towards your clients rather than having them go and seek it for themselves.

But while Active Desktop was being switched off everywhere, Userland software was pushing content in the first syndicated format, the <scriptingNews> format. Netscape was also developing RSS, and in 1999 both formats were merged to produce RSS 0.91 and, shortly afterwards, 0.92. However, there was deep disagreement around how the format should evolve, and the specifications spilt, with RSS 2.0 being controlled first by Userland and now the Berkman Center at Harvard Law School under a creative commons license. RDF 1.0 is currently managed by a community user group.

Now Atom, yet another specification, is being proposed with the backing of Google (the now owners of Blogger) and SixApart (owners of Moveable Type and TypePad). Just recently, a merger has been offered by David Winer, the founder of UserLand software, to combine the new features in Atom with the ease of the RSS format, thereby simplifying the whole process of writing and consuming feeds and, most importantly, getting the technology adopted within the mainstream.

Today, developers have a dizzying array of specifications and formats to support if we wish to consume these feeds on our Websites. For our purposes, the different versions of "RSS" (i.e. 0.91, 0.92 and 2.0) can be treated as one; the changes to the specifications have made the core features backwards-compatible. However, RDF files use different elements and attributes to define syndicated content, so this needs to be handled differently than RSS. And, as the ever-changing Atom specification becomes more widely adopted, we also need a solution that lets us standardise all the specifications into a single format. This must be a format that we can process within our applications, but which also allows us easily to update the way in which we parse the files.

Using XSLT, which is akin to stylesheets for XML, we can parse each format and produce a flatter, simpler format that we can use to populate a DataList object on our site for display. We can alter the XSLT file without changing our code, which makes the model adaptable to any future changes to the specifications.

Creating a Customised DataList

Let’s look how our control will work. I’ve chosen the DataList as the base control we’ll use, due to the fine-grained control it provides us over the presentation of data.

First, we take in the feed the location of which is set in a custom property on our new control. We then apply our style sheet to the incoming feed to produce a common format that we parse to populate our DataList.

As with any control to which we wish to add functionality, we need to create a class that inherits its behaviour from the base control. In our case, this is the System.Web.UI.WebControls.DataList class. Create a new C# class file called RSSDataList.cs and begin with the following code:

namespace SitePoint 
{
   [ToolboxData("<{0}:RSSDataList runat=server></{0}: RSSDataList >")]
   public class RSSDataList: System.Web.UI.WebControls.DataList
   {  
   }
}

Notice the ToolboxData attribute that’s defined for the class. This is the code that will represent our control when we use Visual Studio .NET to drop the control onto our ASP.NET Web page. If you’re using Visual Studio .NET, this is added automatically.

We need a property to store the URL of the feed we wish to view from our control:

private string _location;
   
   [Bindable(true), Category("Behaviour"), DefaultValue("")]  
   public string Location  
   {
     get
     {
       return _location;
     }

     set
     {
       _location = value;
     }
   }

The use of property accessors (get and set) is good programming practise. It allows us to control both the setting and retrieval of our new variable, while providing us with a private variable to which we along have access. We could, for instance, include validation within the set accessor for completeness.

As it stands, we have a DataList control with an added property -- the location of the URL. It's now time to get to the nitty gritty and parse our XML files. To do this, we need to define our XSLT file and the format we'll produce, which will be used to bind with the DataList.

There are 3 different display objectives we wish to extract from our XML file and display: the title of a post, its link, and a description. How we derive these items is up to our stylesheet. For example, with RSS, we can add Permalinks (permanent links to resources) and date information to the description of the post. Indeed, we could build in support for a whole range of extensions and elements in our description (slash:comments, for example).

Hence, the flattened, generic format that we'll parse will have the following format:

<feed> 
 <item>
   <title>Title of post</title>
   <link>Link to post</link>
   <description>Description of post</description>
 </item>
</feed>

In addition, we need to be kind to our feed providers, and to ourselves. Downloading the feed every time we have a request will slow down our bandwidth and might prompt the provider to block our future requests. To control performance, we will employ caching to serialise the contents of our transformed feed. The object will remain in the cache for 60 minutes by default. However, there are elements within RSS called TTL (time to live), which define the exact lifetime of the feed. To parse this value, however, will add complications to our design, but it's a way in which the control could be extended in the future.

To summarise, then, our XSLT file that converts the flavours of RSS and RDF looks like this:

<?xml version="1.0" encoding="ISO-8859-1"?> 
<xsl:stylesheet version="1.0" xmlns:rss="http://purl.org/rss/1.0/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<xsl:output omit-xml-declaration="yes" />
<xsl:template match="rss|/rdf:RDF">
<feed>
 <xsl:apply-templates select="channel/item|/rdf:RDF/rss:item"/>
</feed>
</xsl:template>

<xsl:template match="channel/item|/rdf:RDF/rss:item">
 <item>
   <title>
     <a href="{link|rss:link}">
       <xsl:value-of select="title|rss:title"/>
     </a>
   </title>
   
   <link>
     <xsl:value-of select="link|rss:link"/>
   </link>
   
   <description>
     <p><xsl:value-of select="description|rss:description"/></p>
     <p><xsl:value-of select="pubDate|rss:pubDate"/></p>
   </description>
 </item>
</xsl:template>
 
</xsl:stylesheet>

This style sheet looks for the channel or item elements within both RSS and RDF files and applies a template to redisplay them. Notice that, in the description element, we have combined both the description of the post and the publishing date of the post (pubDate). This is where you can get inventive and add support for all those extra tags to make your display shine.

To process the transformation, and bind the resulting XML to the DataList, we create a new method called bindToRSS() in our RSSDataList control:

public void bindToRSS() 
{
 System.IO.Stream str = new System.IO.MemoryStream();
 System.Xml.XPath.XPathDocument doc;

 // Create the XslTransform.
 System.Xml.Xsl.XslTransform xslt = new System.Xml.Xsl.XslTransform();

 // Load the stylesheet that creates XML Output.
 xslt.Load(System.Web.HttpContext.Current.Server.MapPath("rss.xsl"));

 // Load the XML data file into an XPathDocument.
 // if this is in the cache, grab it from there...
 if (System.Web.HttpContext.Current.Cache["rss"+_location] == null)
 {
   // No cache found.
   doc = new System.Xml.XPath.XPathDocument(_location);
   // Add to cache.
   System.Web.HttpContext.Current.Cache.Add("rss"+_location, doc, null, DateTime.Now.AddMinutes(60), TimeSpan.Zero, CacheItemPriority.High, null);
 }
 else
 {
   // Cache found.
   doc = (XPathDocument)System.Web.HttpContext.Current.Cache["rss" + _location];
 }

 // Create an XmlWriter which will output to our stream.  
 System.Xml.XmlWriter xw = new System.Xml.XmlTextWriter(str, System.Text.Encoding.UTF8);

 // Transform the feed.
 xslt.Transform(doc, null, xw, null);

 // Flush the XmlWriter and set the position of the stream to 0
 xw.Flush();
 str.Position = 0;

 // Create a dataset to bind to the control.    
 DataSet ds = new DataSet();
 ds.ReadXml(str);

 // Select the data source, and bind.
 this.DataSource = ds.Tables[0].DefaultView;
 this.DataBind();

 // Close the writer and thereby free the memory stream.
 xw.Close();

}

We use streams to store our documents and transformations as there is every chance this control is going to be used by multiple users viewing different feeds. If we saved our results to file, we would run into problems as we tried to ensure we displayed the requested feed, and not some other user's request that had overwritten our input! Using memory-based streams, however, each instance of our control has its own storage within the main memory, which makes it suitable for multiple user usage.

Notice how an XmlWriter is employed to provide our stream str with an interface that's suitable for us to write XML. Once the transformation has been made, we need to save (or flush) the contents of the XmlWriter to our stream as, at present, our stream is still empty. After we've written the XML data, the pointer position within the stream will be at the end of the data. Before we read it, we need to set this pointer back to the beginning of the stream.

Once we have filled our memory stream with XML in our flatter format, we can see why a DataList is so suitable for this control. By creating a data set from the XML file, and binding it directly to the DataList, we've parsed and made the feed available for presentation in just 4 lines of code:

// Create a dataset to bind to the control.    
 DataSet ds = new DataSet();
 ds.ReadXml(str);

 // Select the data source, and bind.
 this.DataSource = ds.Tables[0].DefaultView;
 this.DataBind();

Caching

Another of our design goals was to cache the feeds to maintain good performance. The cache saves the XPathDocument instance which contains the feed specified in the Location variable. By caching this, our page will only download the feed once per hour, rather than downloading it each and every time it is requested.

Notice that we need to grab the running instance of the cache as our control isn't tied to any particular Web application. System.Web.HttpContext.Current provides the currently running HttpContext, which includes the familiar Response and Request objects and the Cache object.

Giving the cache an expiration time of 60 minutes is a good idea. When we check to see if an entry exists in the cache, we can also check whether the feed has expired as, after 60 minutes, the cached entry will be removed. Hence, this is a simple method of checking and setting the cache:

if (System.Web.HttpContext.Current.Cache["rss"+_location] == null)
 {
   // No cache found.
 }
 else
 {
   // Cache found.
 }

Now that you've completed the code of the control, you must compile the RSSDataList.cs file into a .NET assembly: RSSDataList.dll. If you don't know how to do this, it's covered in Kevin Yank's article, Object Oriented C# for ASP.NET Developers.

DataList Templates

We now have 3 elements exposed to our DataList: title, link, and description. The display of these elements is handled within the Itemtemplate section of the DataList declaration. This process is very simple.

<asp:DataList ... >  
 
  <ItemTemplate>  
     ... template definition here  
  </ItemTemplate>  
 
</asp:DataList>

Within the ItemTemplate section, we can add markup and styles. At any point at which we want to display one of our elements (title, link, or description), we add the following inline code to tell ASP.NET to insert the contents of the element:

<%# DataBinder.Eval(Container.DataItem, "foo") %>

(Here, "foo" is changed to match the element to add.)

For example, here's a completed item template, which shows the title of a post in a large font, and the description underneath in a new paragraph break:

<sitepoint:RSSDataList runat="server">  
 
 <ItemTemplate>  
   <p><font size="4">  
     <%# DataBinder.Eval(Container.DataItem, "title") %>  
   </font></p>  
   <p>  
     <%# DataBinder.Eval(Container.DataItem, "description") %>  
   </p>  
 </ItemTemplate>  
 
</sitepoint:RSSDataList>

To conclude, let's walk through the process of consuming a feed using this new control.

Adding the Control to the Visual Studio Toolbox
  1. Right click on the control toolbox and select "Add / Remove Items�"
  2. From the Customize Toolbox dialog (shown below), click Browse� and select the assembly that contains the RSSDataList.cs class that holds the control:
  3. 1324_image1

  4. Select the RSSDataList control from the list, and make sure the check box is ticked. Click OK to add the control to the toolbox.
Defining the Control in the .aspx page
  1. Either drag the RSSDataList control onto your form, or add the following declarations to your .aspx page:

    <%@ Register TagPrefix="sitepoint" Namespace="SitePoint" Assembly="RSSDataList" %>  
     
    <sitepoint:RSSDataList id="RSSDataList1" runat="server">  
    </sitepoint:RSSDataList>
  2. Add the ItemTemplate we saw above to the RSSDataList tag:

    <%@ Register TagPrefix="sitepoint" Namespace="SitePoint" Assembly="RSSDataList" %>  
     
    <sitepoint:RSSDataList id="RSSDataList1" runat="server">  
     <ItemTemplate>  
       <p><font size="4">  
         <%# DataBinder.Eval(Container.DataItem, "title") %>  
       </font></p>  
       <p>  
         <%# DataBinder.Eval(Container.DataItem, "description") %>  
       </p>  
     </ItemTemplate>  
    </sitepoint:RSSDataList>
  3. Using the property selector in Visual Studio, you can define the feed you whish to associate with the control through the Location variable:

    1324_image2

    Alternatively, you can add this variable to the tag definition earlier:

    <sitepoint:RSSDataList id="RSSDataList1" Location="http://www.sitepoint.com/recent.rdf" runat="server">  
     ...  
    </sitepoint:RSSDataList>

    Or, of course, through code:

    private void Page_Load(object sender, System.EventArgs e)  
    {  
     RSSDataList1.Location = "http://www.sitepoint.com/recent.rdf";  
    }

Binding the Feed to the Control

Finally, you need to call the method bindToRSS() once you have set the Location property:

private void Page_Load(object sender, System.EventArgs e)  
{  
 RSSDataList1.Location = "http://www.sitepoint.com/recent.rdf";  RSSDataList1.bindToRSS();  
}

Make sure you have your XSLT file rss.xsl stored in the root of your application directory. That's all there is to displaying RSS and RDF files on your site!

1324_image3

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.