Generating RSS and ATOM Feeds In WebMatrix

I've previously looked at how to generate RSS feeds for both Web Forms and MVC using a variety of techniques, so it is only right that I look at a couple of ways to do this in Web Pages - the Razor based web development model supported by WebMatrix.

Just to recap, RSS, or Really Simple Syndication is an XML-based way to share or distribute content. ATOM was designed to overcome some of the perceived issues that afflicted RSS, and enjoys some support around the place too. Either way, most RSS readers accept ATOM feeds and vice-versa, so if you are asked to produce either ATOM or RSS, this article should help you.

I am using my Books database for the examples. Ideally your data will have a date created or similar against it. The sample database only has a date that the book was published, so it's not perfect but should serve for the purposes of illustration. The first example looks at generating RSS from LINQ to XML. The second and third look at producing both RSS and ATOM from a third party library - Argotic.

LINQ TO XML

LINQ to XML is a nice feature which was added in C# 3. It makes programming against XML more enjoyable than previous APIs. The following code is straightforward, and even mirrors the structure of the resulting XML to an extent:

@using System.Xml.Linq;
@{
    Layout = null;
    var db = Database.Open("Books");
    var url = "/Book/{0}";
    var sql = "Select BookId, Title, Description, DatePublished From Books";
    var items = db.Query(sql);
    var rss = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
        new XElement("rss",
          new XAttribute("version", "2.0"),
            new XElement("channel",
              new XElement("title", "Books News Feed"),
              new XElement("link", Functions.GetSiteUrl("~/rss")),
              new XElement("description", "Latest additions to Books"),
              new XElement("copyright", "(c)" + DateTime.Now.Year),
                items.Select(item => 
                    new XElement("item",
                    new XElement("title", item.Title),
                    new XElement("description", item.Description),
                    new XElement("link", Functions.GetSiteUrl(String.Format(url, item.BookId))),
                    new XElement("pubDate", item.DatePublished.ToString("R"))
                   ))
              )
        )
      );
    Response.ContentType = "text/xml";
    rss.Save(Response.OutputStream);
}

Some points of note: The first thing I did in this file - after referencing System.Xml.Linq - was to set the Layout page to null. This page should never have a layout page associated with it. Its job is to render pure XML. Having a layout page applied will cause the XML to become corrupted by HTML being injected into the output. You might think "but I know that - I just won't set a layout page for this file!" and indeed this will probably suffice most of the time. However, all it takes is for you, or someone else at some stage in the future to set the layout page in PageStart, for it to affect every file in the same or child folders, and Bang! your feed is broken. For this reason, you should get into the habit of declaring Layout = null at the top of every file like this.

The second point of note is the GetSiteUrl() function. I borrowed this from pranavkm, who I believe works for Microsoft. It's a nice little utility method that constructs the URL for a link based on the current environment. That means that the link will be generated from http://localhost:xxxxx during testing, but the full domain in production. No messing about from one deployment to another. The code for the function is this:

@functions {
  public static Uri GetSiteUrl(string url = "~/") {
    url = Request.Url.GetLeftPart(UriPartial.Authority) + VirtualPathUtility.ToAbsolute(url);
    return new Uri(url);
  }
}

I hadn't really looked at some of the obscurer methods that belong to Uri's before this. I might well have to explore them in a future article. In the meantime, back to the RSS feed. Once the XDocument has been constructed in memory, it is serialised to the Response output stream, with the correct content type having been set for the Response.

Argotic RSS

The Argotic Syndication Framework is a comprehensive library available both via Codeplex and NuGet. It includes a lot of tools for generating syndicated content or for consuming it, and seem to be pretty popular. Since it is available via NuGet, it is also available from the WebMatrix Package Manager. When you log into the Web Pages Administration tool, make sure you choose Default (All) as your packages source to access the whole of NuGet. From that point, you can simply search for "Argotic" and choose to install the Core library. Two other dependencies are also intalled - Argotic Common and Argotic Extensions. Once you have that installed, you can start working with the library:

@using Argotic.Syndication
@{
    Layout = null;
    var db = Database.Open("Books");
    var url = "/Book/{0}";
    var sql = "Select BookId, Title, Description, DatePublished From Books";
    var books = db.Query(sql);

    var feed = new RssFeed 
                   { 
                       Channel.Link = Functions.GetSiteUrl("~/argotic_rss"),
                       Channel.Title = "Books News Feed",
                       Channel.Description = "Latest additions to Books"
                   };

    foreach (var book in books)
    {
        var item = new RssItem
                       {
                           Title = book.Title,
                           Link = Functions.GetSiteUrl(String.Format(url, book.BookId)),
                           Description = book.Description,
                           PublicationDate = book.DatePublished
                       };
        feed.Channel.AddItem(item);
    }

    Response.ContentType = "text/xml";
    feed.Save(Response.OutputStream);
}

Argotic offers three types of Feed object - RssFeed, AtomFeed and GenericSyndicationFeed. We are only interested in the RssFeed object for this exercise. The library is really simple to use, and actually uses less code than the LINQ to XML version. It also offers the benefit of generating valid XML for you, so you don't have to worry about typos in your LINQ to XML XElement names. It's really difficult to discuss the code in any detail, as it is so self-explanatory. This is probably one of the best indicators of a very well designed API.

Argotic ATOM

Now that you have the Argotic library available to you, you can try the ATOM version:

@using Argotic.Syndication
@{
    Layout = null;
    var db = Database.Open("Books");
    var url = "/Book/{0}";
    var sql = "Select BookId, Title, Description, DatePublished From Books";
    var books = db.Query(sql);

    var feed = new AtomFeed
                   {
                       Id = new AtomId(Functions.GetSiteUrl()),
                       Title = new AtomTextConstruct("Books News Feed"),
                       UpdatedOn = DateTime.Now,
                   };

    feed.Authors.Add(new AtomPersonConstruct("Mikesdotnetting"));

    var selfLink = new AtomLink
                       {
                           Relation = "self", 
                           Uri = Functions.GetSiteUrl("~/argotic_atom")
                       };
    feed.Links.Add(selfLink);
    foreach (var book in books)
    {
        var entry = new AtomEntry
                        {
                            Id = new AtomId(Functions.GetSiteUrl(String.Format(url, book.BookId))),
                            Title = new AtomTextConstruct(book.Title),
                            UpdatedOn = book.DatePublished,
                            Summary = new AtomTextConstruct(book.Description)
                        };

        var alternateLink = new AtomLink
                                {
                                    Relation = "alternate",
                                    Uri = Functions.GetSiteUrl(String.Format(url, book.BookId))
                                };
        entry.Links.Add(alternateLink);
        feed.AddEntry(entry);
    }
    Response.ContentType = "application/atom+xml";
    feed.Save(Response.OutputStream);
}

In principal there are not too many differences between Argotic ATOM and RSS. One is constructed out of RssItem objects, while the other is a collection of AtomEntry objects. Interestingly, "application/atom+xml" is an official MIME type, whereas "application/rss+xml" is not. Choices for MIME Types for RSS are "application/xml" or "text/xml".