Recently, I've been going through the standard fatherhood ritual of trying to setup of a webpage to share out photos of my kid. This naturally involves creating an RSS feed so that family can subscribe to the new image list.

I wanted to be able to write simple C# code like this to produce the feed:


    static void WriteWholeFile()
    {
        XmlWriter xml = new XmlTextWriter(new StreamWriter("myfeed.xml"));
        RssWriter rss = new RssWriter(xml);

        rss.WriteHeader("My Mini blog", "http://xyz.com", "My site of baby pictures", null);
        rss.WriteItem(
            "First item",
            "Hi! Here's the <b>first</b> item",
            new System.Uri("http://xyz.com/1.html"),
             new DateTime(1999, 12, 1));

        rss.WriteItem(
            "Second item",
            "Here's the <b>2nd</b> item.",
            new System.Uri("http://xyz.com/2.html"),
            new DateTime(1988, 10, 4));
        rss.Close();
        xml.Close();
    }

Here's some trivia about some of the decisions I made here:

  • I'm no RSS/XML/Web expert, so I have no doubt there are many better implementations out there.
  • Copying one of these other writers would violate the unspoken rules of this standard fatherhood ritual. (as would using Flickr)
  • My main goal was simplicity, as illustrated by the usage snippet above.
  • It's producing valid RSS 2.0 feeds. You can validate the feeds by URL at http://feedvalidator.org, or by pasting in the text at http://validator.w3.org/feed. And they also work in SharpReader (which is what my target audience is using).
  • DateTimeFormatInfo is a live-saver to get the the DateTime into the date format needed for an RSS feed.
  • Since RSS is just XML, I choose to build RSSWriter on an XmlWriter. This lets the caller have great flexibility about where the XML goes (a local file, a string, a node in an XML Dom)
  • The RSSWriter does not own the underlying XmlWriter, and so RSSWriter.Close() does not close the underlying streams.
  • I avoided XML Serialization (the method Dare used in RSSBandit) because I wanted a simple, forward writing, stateless, writer. This is similar to using XmlWriter over the XML Dom.

Here's the code:  [Updated: made some minor tweaks, including rename RSS-->Rss per fxcop guidelines]


using System;
using System.Xml;
using System.IO;

// Class to write out RSS
// Expected Usage:
//  rss = new RSSWriter(...);
//  rss.WriteHeader(...);
//    rss.WriteItem(..);
//    rss.WriteItem(..);
//    rss.WriteItem(..);
//  rss.Close();

// Validate feeds by URL: http://feedvalidator.org, or http://validator.w3.org/feed
// Code for RSS writer from http://blogs.msdn.com/jmstall  
class RssWriter
{
    XmlWriter m_writer;
    public RssWriter(XmlWriter tw)
    {
        m_writer = tw;
    }

    // Write header for RSS Feed
    // Parameters:
    // title - Pretty title of the RSS feed. Eg "My Pictures"
    // link - optional URL to link RSS feed back to a web page.
    // description - more verbose human-readable description of this feed.
    // generator - optional string for 'generator' tag.
    public void WriteHeader(string title, string link, string description, string generator)
    {
        m_writer.WriteStartElement("rss");
        m_writer.WriteAttributeString("version", "2.0");
        m_writer.WriteStartElement("channel");
        m_writer.WriteElementString("title", title);

        if (link != null)
        {
            m_writer.WriteElementString("link", link); // link to generated report.
        }
        m_writer.WriteElementString("description", description);

        if (generator != null)
        {
            m_writer.WriteElementString("generator", generator);
        }

        m_writer.WriteElementString("lastBuildDate", ConvertDate(new DateTime()));

    }

    // Write out an item.
    // title - title of the blog entry
    // content - main body of the blog entry
    // link - link the blog entry back to a webpage.
    // time - date for the blog entry.
    public void WriteItem(string title, string content, System.Uri link, DateTime time)
    {
        m_writer.WriteStartElement("item");
        WriteItemBody(m_writer, title, content, link, time);
        m_writer.WriteEndElement(); // item
    }

    // Write just the body (InnerXml) of a new item
    public static void WriteItemBody(XmlWriter w, string title, string content, System.Uri link, DateTime time)
    {
        w.WriteElementString("title", title);
        if (link != null) { w.WriteElementString("link", link.ToString()); }
        w.WriteElementString("description", content); // this will escape
        w.WriteElementString("pubDate", ConvertDate(time));
    }

    // Close out the RSS stream.
    // this does not close the underlying XML writer.
    public void Close()
    {
        m_writer.WriteEndElement(); // channel
        m_writer.WriteEndElement(); // rss
    }

    // Convert a DateTime into the format needed for RSS (from RFC 822).
    // This looks like: "Wed, 04 Jan 2006 16:03:00 GMT"
    static string ConvertDate(DateTime t)
    {

        // See this for help on format string
        // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconcustomdatetimeformatstrings.asp 
        DateTime t2 = t.ToUniversalTime();
        return t2.ToString(@"ddd, dd MMM yyyy HH:mm:ss G\MT");
    }

}