When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, March 18, 2009

Syndicating and Consuming RSS 1.0 (RDF) Feeds in ASP.NET 3.5

By Scott Mitchell


Introduction


Websites that produce new content on a regular basis should include a syndication feed, which is a specially formatted XML file that includes a summary of the most recently published items. Virtually all blogs, news sites, and social media sites have a syndication feed, and 4Guys is no exception. The 4GuysFromRolla.com syndication feed contains the most recent articles. Syndication feeds are meant to be consumed by computers. Sites like Technorati parse the syndication feeds from blogs and use that data to determine the topic du jour. Also, syndication feeds are commonly used by websites to display the latest headlines from related sites. For example, an ASP.NET community website could consume the 4GuysFromRolla.com syndication feed to display the latest 4Guys headlines.

Until recently, there was no built-in support for creating or consuming syndication feeds in the .NET Framework. That changed with the release of the .NET Framework version 3.5, which included a new namespace: System.ServiceModel.Syndication. This new namespace includes a handful of classes for working with syndication feeds. As aforementioned, syndication feeds are XML files, and for the syndication feed to be of any use it must conform to one of the popular syndication feed standards. The two most popular syndication feed standards are RSS 2.0 and Atom 1.0, and these are the standards supported by the classes in the System.ServiceModel.Syndication namespace. But there is a third format that, while not as popular as RSS 2.0 or Atom 1.0, is still used. That standard is RSS 1.0.

The good news is that with a little bit of work we can create a class that works with the RSS 1.0 standard and have this class used by the syndication feed-related classes in the .NET Framework 3.5 can be. This article introduces a free library, skmFeedFormatters, which you can use in an ASP.NET 3.5 application to create and consume RSS 1.0 feeds. (This same concept could be applied to creating and parsing Atom 0.3 feeds, as well.) Read on to learn more!

- continued -

A Brief History of Syndication Feed Standards


The idea of online syndication feeds was first proposed by Dave Winer back in 1997 as a way for exposing the content of his blog in a machine-readable format. Dave called his standard RSS for Really Simple Syndication. Over the years he continued fine tuning the standard until 2003, at which point the standard was frozen with the release of RSS 2.0. RSS 2.0 is a very popular syndication feed standard in large part because it is very simple and straightforward, making it easy to implement and parse. For example, the 4Guys syndication feed adheres to the RSS 2.0 standard.

Around the same time a group from Netscape set about crafting a syndication standard based on Resource Description Framework (RDF), a standard proposed by the W3C for representing information about resources on the web. To make things as confusing as possible, this new syndication feed standard was named RDF Site Summary, or RSS for short, resulting in two differing standards with the same acronym! Eventually, this work by Netscape (and later O'Reilly) became known as RSS 1.0 and Dave's standard as RSS 2.0, although sometimes the RSS 1.0 standard is referred to as RDF to help remove any ambiguity.

After the RSS 2.0 standard was frozen in 2003, Sam Ruby proposed a new standard to overcome RSS 2.0's shortcomings. This new standard was defined by the community and named Atom. The first major release was Atom 0.3. After some more changes, the final, standardized version was released as Atom 1.0. (Unfortunately, some sites still syndicate content using the Atom 0.3 standard.)

The two most widely used standards are RSS 2.0 and Atom 1.0. However, some sites still use RSS 1.0 or Atom 0.3. For example, the popular tech news and discussion site Slashdot.org syndicates its content using RSS 1.0.

Creating and Consuming Syndication Feeds in ASP.NET 3.5


The .NET Framework version 3.5 introduced a new namespace, System.ServiceModel.Syndication, with a number of classes for creating and consuming syndication feeds. These classes are divided into two categories: classes that model a syndication feed and the items in a feed, and classes that are responsible for transforming the classes that model syndication feeds into the appropriate XML and vice-a-versa.
  • Classes That Model Syndication Feeds and Items
    • SyndicationFeed - represents a syndication feed. Has properties like Title, Description, Links, and Items. The Items property represents the collection of content items expressed in the feed.
    • SyndicationItem - represents a specific syndication feed item and includes properties like Title, Summary, PublishDate, Authors, and so on.
  • Classes That Transform Syndication Feeds To/From XML
    • Rss20FeedFormatter - can take a SyndicationFeed object and turn it into XML that conforms to the RSS 2.0 specification. Also, can be used to consume a properly-formatted RSS 2.0 feed, turning the XML into a SyndicationFeed object with its properties set based on the data in the consumed XML.
    • Atom10FeedFormatter - same as the Rss20FeedFormatter, but uses the Atom 1.0 standard.
The .NET Framework 3.5 only ships with two feed formatter classes for the RSS 2.0 and Atom 1.0 specifications. Consequently, you cannot syndicate or consume RSS 1.0 or Atom 0.3 feeds out of the box. In his blog entry How to upgrade Atom 0.3 feeds on the fly with a custom XmlReader for use with WCF Syndication APIs, Daniel Cazzulino shows how you can use XSLT to transform Atom 0.3 markup to Atom 1.0-compliant markup on the fly.

A more formal approach, in my opinion, is to create your own feed formatter class. The Rss20FeedFormatter and Atom10FeedFormatter classes in the System.ServiceModel.Syndication namespace derive from the base class SyndicationFeedFormatter. We can create our own formatter class by extending this base class. For example, we could create an Atom03FeedFormatter class and use it to turn a SyndicationFeed object into conforming XML, or consume an Atom 0.3 XML feed and generate a corresponding SyndicationFeed object.

The download available for download at the end of this article includes a class named Rss10FeedFormatter, which can be used to create and consume RSS 1.0 feeds. The download also includes a demo website showing this class in action. The remainder of this article explores the Rss10FeedFormatter class. (You could take the concepts presented in this article to create an Atom03FeedFormatter class, or a class to work with other, more esoteric feed formats, should the need arise.)

How Do You Create and Consume Syndication Feeds Using the New Classes in the .NET Framework Version 3.5?
This article does not walk through using the SyndicationFeed class and the Rss20FeedFormatter and Atom10FeedFormatter classes to create and consume syndication feeds. It assumes the reader is already familiar with performing these tasks.

For a detailed look at creating and consuming feeds using the classes in the System.ServiceModel.Syndication namespace check out my article, How To Create a Syndication Feed For Your Website.

A Quick Overview of the RSS 1.0 Standard


Before we get started examining the code for the Rss10FeedFormatter class, let's first take a moment to explore the RSS 1.0 standard, which is available online at http://web.resource.org/rss/1.0/spec. Like an RSS 2.0 feed, an RSS 1.0 feed is designed to express information about recently published content items. In a nutshell, an RSS 1.0 feed starts with the XML element <RDF>. Following that, a <channel> element describes information about the feed, including the feed's title, description, a link, and so on. The <channel> element also must contain an <items> element that serves as a table of contents of sorts. The feed's items come after the <channel> element and are each represented by an <item> element. The <item> element has children elements that describe the item's title, link, and description.

RSS 1.0 is more complex than RSS 2.0 in part because RSS 1.0 includes XML namespaces. The root element (<RDF>) must include two namespaces: http://www.w3.org/1999/02/22-rdf-syntax-ns# and http://purl.org/rss/1.0/, and these must appear in the root element like so:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/">
   ...
</rdf:RDF>

Note that the rdf namespace prefix must be used with the root element name, as in: <rdf:RDF>. The rdf prefix is also used in other elements in the document.

One feature that makes RSS 2.0 so simple is that there are no attributes, save for the version attribute in the root <rss> element. However, in an RSS 1.0 document there are many elements with attributes. For example, each item must have an rdf:about attribute that provides a URI that uniquely identifies the item within the document. (Usually the link to the content item is used for this attribute value.) This rdf:about attribute is referenced in the "table of contents" in the <channel> element.

An example RSS 1.0 feed composed of three content items follows.

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/">
   <channel rdf:about="http://www.example.com/">
      <title>Example RSS 1.0 Feed</title>
      <link>http://www.example.com/</link>
      <description>An example RSS 1.0 feed.</description>

      <items>
         <rdf:Seq>
            <rdf:li rdf:resource="http://www.example.com/LearnRSS10.aspx" />
            <rdf:li rdf:resource="http://www.example.com/Lessons/FeedLesson.aspx" />
            <rdf:li rdf:resource="http://www.example.com/LearnAtom10.aspx" />
         </rdf:Seq>
      </items>
   </channel>
   
   <item rdf:about="http://www.example.com/LearnRSS10.aspx">
      <title>Learn About RSS 1.0!</title>
      <link>http://www.example.com/LearnRSS10.aspx</link>
      <description>Learn all about the RSS 1.0 standard.</description>
   </item>

   <item rdf:about="http://www.example.com/Lessons/FeedLesson.aspx">
      <title>Create Your Own Syndication Feed</title>
      <link>http://www.example.com/Lessons/FeedLesson.aspx</link>
      <description>This tutorial walks through creating your own syndication feed.</description>
   </item>

   <item rdf:about="http://www.example.com/LearnAtom10.aspx">
      <title>Master Atom 1.0</title>
      <link>http://www.example.com/LearnAtom10.aspx</link>
      <description>Become fluent in Atom 1.0 by reading this article.</description>
   </item>
</rdf:RDF>

Creating a Syndication Feed Formatter Class


The syndication feed formatter classes in the System.ServiceModel.Syndication namespace derive from the SyndicationFeedFormatter and are responsible for turning a SyndicationFeed object into corresponding XML and parsing XML and generating a SyndicationFeed object. The SyndicationFeedFormatter class has a Feed property that holds the SyndicationFeed object being worked on by the formatter. The SyndicationFeedFormatter class defines four abstract methods, which are methods that the base class must override:
  • CanRead(XmlReader) - returns a Boolean value that indicates whether the formatter can parse the supplied feed.
  • ReadFrom(XmlReader) - parses the supplied XmlReader, reading its contents into the SyndicationFeed object stored in the Feed property.
  • WriteTo(XmlWriter) - writes the information stored by the SyndicationFeed object in the Feed property to the passed-in XmlWriter object.
  • CreateFeedInstance() - creates and returns a new SyndicationFeed object.
The ReadFrom method is used to turn XML into a SyndicationFeed object, whereas the WriteTo method transforms a SyndicationFeed object into XML.

The SyndicationFeedFormatter class also defines an abstract string property named Version, which indicates the syndication feed standard version being used. The Rss10FeedFormatter returns the value "Rss20", for example.

To create a syndication feed formatter class, start by creating a class that derives from SyndicationFeedFormatter. The following code shows the shell of the Rss10FeedFormatter class In addition to the abstract methods and property, this shell also includes two constructors and two read-only properties. The first constructor is the default constructor, while the second one accepts a SyndicationFeed object as an input parameter and passes it to the base class's constructor. This base constructor called by this latter constructor assigns the supplied SyndicationFeed object to the base class's Feed property. The two read-only properties simply define the namespaces used by the RSS 1.0 specification.

public class Rss10FeedFormatter : SyndicationFeedFormatter
{
   public Rss10FeedFormatter() { }
   public Rss10FeedFormatter(SyndicationFeed feed) : base(feed) { }

   public override string Version
   {
      get { return "Rss10"; }
   }

   public virtual string RdfNamespaceUri
   {
      get { return "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; }
   }

   public virtual string NamespaceUri
   {
      get { return "http://purl.org/rss/1.0/"; }
   }

   public override bool CanRead(System.Xml.XmlReader reader)
   {
      ...
   }

   protected override SyndicationFeed CreateFeedInstance()
   {
      ...
   }

   public override void ReadFrom(System.Xml.XmlReader reader)
   {
      ...
   }

   public override void WriteTo(System.Xml.XmlWriter writer)
   {
      ...
   }
}

Consuming RSS 1.0 XML


The SyndicationFeedFormatter base class defines two abstract methods for reading an XML document: CanRead(XmlReader) and ReadFrom(XmlReader). The CanRead(XmlReader) method returns a Boolean value indicating whether the feed can be read. We can only read an RSS 1.0 feed if it starts with the expected root element, namely <RDF> with the namespace http://www.w3.org/1999/02/22-rdf-syntax-ns#. This is precisely what the CanRead method checks for:

public class Rss10FeedFormatter : SyndicationFeedFormatter
{
   ...

   public override bool CanRead(System.Xml.XmlReader reader)
   {
      if (reader == null)
         throw new ArgumentNullException("reader");

      return reader.IsStartElement("RDF", this.RdfNamespaceUri);
   }
}

The real work occurs in the ReadFrom(XmlReader) method, which is responsible for parsing through the XML documents and creating a corresponding SyndicationFeed object populated with SyndicationItem objects for each item defined in the feed. This code consumes the bulk of the Rss10FeedFormatter class. An abbreviated version is shown below. Specifically, the code illustrated here reads in the channel elements and assigns the values in a SyndicationFeed object named result.

public class Rss10FeedFormatter : SyndicationFeedFormatter
{
   ...

   public override void ReadFrom(System.Xml.XmlReader reader)
   {
      ...
      
      reader.ReadStartElement("channel");    // Read in <channel>
      while (reader.IsStartElement())       // Process <channel> children
      {
         if (reader.IsStartElement("title"))
            result.Title = new TextSyndicationContent(reader.ReadElementString());
         else if (reader.IsStartElement("link"))
            result.Links.Add(new SyndicationLink(new Uri(reader.ReadElementString())));
         else if (reader.IsStartElement("description"))
            result.Description = new TextSyndicationContent(reader.ReadElementString());         
         else
            reader.Skip();
      }
      reader.ReadEndElement();            // Read in </channel>
      
      ...
   }
}

The above code reads to the <channel> element and then loops through the children elements. If it finds a <title> element it extracts the value and assigns it to the SyndicationFeed object's Title property. Likewise, if it finds a <link> element it extracts its value, creates a new SyndicationLink object, and adds is to the SyndicationFeed object's Links collection. Any element other than <title>, <link>, or <description> is skipped.

Note that the formatter does not do any validation on the RSS 1.0 feed it's reading. For example, according to the specification the <channel> element's <title>, <link>, and <description> elements are required. However, no error is raised if these elements are missing.

By the time the ReadFrom method completes, the Feed property has had its relevant properties assigned to the corresponding values from the XML. The demo website included in the download at the end of this article includes a page to test consuming an RSS 1.0 feed (~/ConsumeRSS10.aspx). Here, you can enter the URL to an RSS 1.0 feed. The specified feed can be parsed using the following three lines of code:

// Read in the RSS 1.0 feed from the specified URL
XmlReader reader = XmlReader.Create(urlToRSS10Feed);

// Create a new Rss10FeedFormatter object
Rss10FeedFormatter formatter = new Rss10FeedFormatter();

// Parse the feed!
formatter.ReadFrom(reader);

After the last line of code executes, the formatter.Feed property contains the SyndicationFeed information. You can work with this data programmatically or bind it to a data Web control, such as a GridView or ListView. (The ~/ConsumeRSS10.aspx page binds the results to a ListView.) The following screen shot shows the ~/ConsumeRSS10.aspx page in action when parsing the Slashdot RSS 1.0 feed.

The Slashdot RSS 1.0 feed has been parsed and displayed in a web page.

Syndicating RSS 1.0 XML


The SyndicationFeedFormatter class also includes facilities for generating the XML for an existing SyndicationFeed object, and does so through its WriteTo(XmlWriter) method, which outputs the corresponding XML to the passed-in XmlWriter instance. This code for writing the XML is a bit lengthy, as you might imagine. For the sake of brevity, let's just look at the code that creates the starting <RDF> element, the <channel> element and its first few children elements.

The following code writes out the root node with the required namespace definitions.

public class Rss10FeedFormatter : SyndicationFeedFormatter
{
   ...

   public override void WriteTo(System.Xml.XmlWriter writer)
   {
      // Write <rdf:RDF xmlns:rdf="..." xmlns="...">
      writer.WriteStartElement("rdf", "RDF", this.RdfNamespaceUri);
      writer.WriteAttributeString("xmlns", this.NamespaceUri);

      ... Continued below ...

Next, we need to write out the <channel> element and its children. I've omitted some of the code that determines the value to use for the <channel> element's rdf:about attribute, as well as the <link> element, for brevity.

      ... Continued from above ...
      
      writer.WriteStartElement("channel");    // Write <channel>
      writer.WriteAttributeString("about", this.RdfNamespaceUri, link);

      if (base.Feed.Title == null || string.IsNullOrEmpty(base.Feed.Title.Text))
         throw new ArgumentException("Feed title required for RSS 1.0 feeds.");
      writer.WriteElementString("title", base.Feed.Title.Text);    // Write <title>


      if (link.Length == 0)
         throw new ArgumentException("Feed link required for RSS 1.0 feeds.");
      writer.WriteElementString("link", link);    // Write <link>


      if (base.Feed.Description == null || string.IsNullOrEmpty(base.Feed.Description.Text))
         throw new ArgumentException("Feed title required for RSS 1.0 feeds.");
      writer.WriteElementString("description", base.Feed.Description.Text);    // Write <description>

      ...
   }
}

Unlike the code that consumes a feed, the code that syndicates it performs various checks to ensure that a valid feed is generated. For example, if the SyndicationFeed object being syndicated doesn't include a value for its Title property then the feed is invalid because RSS 1.0 feeds must include a <title> child element within the <channel> element.

The demo website includes the ability to generate a syndication feed using either:

  • Atom 1.0
  • RSS 2.0
  • RSS 1.0
To view these feeds, visit the Feed.aspx page passing in the type through the querystring (either Rss10, Rss20, or Atom). For example, to generate the feed in conformance with the RSS 1.0 standard you would visit: Feed.aspx?Type=Rss10. The code in the Feed.aspx page's code-behind class builds the SyndicationFeed object and its Items based on data from the pubs database. It then uses the appropriate formatter class based on the Type querystring parameter value. In the following code, myFeed is a populated SyndicationFeed object. The feed formatter class used outputs the XML to the Response.OutputStream stream, which returns the markup to the requesting user agent.

XmlWriter feedWriter = XmlWriter.Create(Response.OutputStream);

if (outputAtom)
{
   // Use Atom 1.0      
   Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(myFeed);
   atomFormatter.WriteTo(feedWriter);
}
else if (outputRss20)
{
   // Emit RSS 2.0
   Rss20FeedFormatter rssFormatter = new Rss20FeedFormatter(myFeed);
   rssFormatter.WriteTo(feedWriter);
}
else if (outputRss10)
{
   // Emit RSS 1.0
   Rss10FeedFormatter rssFormatter = new Rss10FeedFormatter(myFeed);
   rssFormatter.WriteTo(feedWriter);

}

feedWriter.Close();

Conclusion


The syndication feed-related classes in the .NET Framework version 3.5 make it a snap to create and consume RSS 2.0 and Atom 1.0 syndication feeds. If you need to create or consume feeds that use an alternative standard you can create a custom feed formatter class. This article showed how to create a formatter class for RSS 1.0 feeds. You are welcome to download and use this code in your web applications.

Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the code associated with this article series
  • Further Reading


  • The RSS 2.0 Specification
  • The RSS 1.0 Specification
  • How To Create a Syndication Feed For Your Website


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article