Build an RSS feed using LINQ to XML
posted by
Jeff
Using LINQ, you can generate an RSS feed with clean code and less work than with traditional XML classes.
More than four years ago, I posted an article demonstrating how to build an RSS feed for your site. I'm happy to report that building this sort of thing is even easier now, thanks in part to C# 3.0 and LINQ to SQL.
LINQ stands for Language Integrated Query, and it gives us the tools to really have fun with collections. LINQ to XML is a LINQ provider that lets us use the same language constructs to query and use XML data. If you haven't read it, I can't suggest picking up a copy of LINQ in Action enough. It's a fantastic book. Rather than getting into what LINQ is really about though, I want to get into how you can use it to build an RSS feed.
The easiest way to explain this is to throw the code at you, then explain it. The code is an HttpHandler, and as such can be designated in web.config if the class is compiled in an assembly, or it can live in an .ashx file.
using System;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Xml;
using System.Xml.Linq;
namespace CoasterBuzz
{
public class NewsRssHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
Cache cache = HttpRuntime.Cache;
var key = "cb_news_rss";
XDocument xml;
if (cache[key] == null)
{
xml =
new XDocument(
new XElement("rss",
new XAttribute("version", "2.0"),
new XElement("channel",
new XElement("title", "CoasterBuzz News Wire"),
new XElement("link", "http://coasterbuzz.com/News.aspx"),
new XElement("description", "Roller coaster and amusement
industry news from the Portal to The Thrill, CoasterBuzz!"),
new XElement("copyright", String.Format("(c){0}, POP World
Media, LLC. All rights reserved", DateTime.Now.Year)),
new XElement("ttl", "15"),
from item in News.GetPageOfNews(30)
select new XElement("item",
new XElement("title", item.Title),
new XElement("description", item.Summary),
new XElement("link", String.Format(
"http://coasterbuzz.com/News/{0}/{1}.aspx", item.NewsID, item.UrlName)),
new XElement("pubDate", item.PostDate.Value.ToString("R"))
)
)
)
);
cache.Insert(key, xml, null, DateTime.Now.AddMinutes(15),
Cache.NoSlidingExpiration);
}
else
xml = (XDocument) cache[key];
context.Response.ContentType = "text/xml";
XmlTextWriter writer = new XmlTextWriter(
context.Response.OutputStream, Encoding.UTF8);
xml.WriteTo(writer);
writer.Close();
}
public bool IsReusable
{
get { return true; }
}
}
}
Starting at the top, the class implements IHttpHandler. Every request that ASP.NET handles is processed by one of these handlers (or in some cases by an IHttpHandlerFactory) and reacts to the request by supplying a response. The ProcessRequest(HttpContext) method takes care of this. The IsReusable property simply tells the framework whether or not you can use the same instance of the class over and over. Our magic has to happen in ProcessRequest(HttpContext).
The first few lines set up some plumbing to see if there is a cached version of our XML document (of the XDocument type, which we'll talk about in a moment). If you've run a site with an RSS feed and looked at your logs, you know that news readers can beat the crap out of those. As such, you don't need to go through the hassle of creating this doc every single time there's a request, going to the database every single time. The ttl value, or "time to live" that you embed into the XML is not always respected by readers either.
LINQ to XML introduces a number of new classes in the System.Xml.Linq namespace that correspond to pieces of XML. The constructors of many of these classes have an overload that can take an array of parameters, meaning you can compose any number of sub-objects to appear in the object you're creating. The end result is our code sample, where we create an entire tree of objects within the constructor of that first XDocument object. The benefit is that it looks a lot like the XML you're trying to output.
If you're wondering exactly what an RSS XML document is supposed to look like, you can read the spec here.
For example, let's take a specific section of our code and break it down:
new XElement("channel",
new XElement("title", "CoasterBuzz News Wire"),
new XElement("link", "http://coasterbuzz.com/News.aspx"),
new XElement("description", "Roller coaster and amusement industry news from the
Portal to The Thrill, CoasterBuzz!"),
new XElement("copyright", String.Format("(c){0}, POP World Media, LLC. All rights
reserved", DateTime.Now.Year)),
new XElement("ttl", "15"),
...
)
In this example, we're creating a new element that will be named "channel." Per the spec, this element has several child elements, including "title," "link" and others. This overload of the XElement constructor takes the name of the element as the first parameter, and an array of objects as its second. We're going to populate that parameter with additional XElement objects, the last of which will be several "item" elements, which in turn will have their own child elements. And they're all composed neatly within our code.
The real magic comes from the next block of code:
from item in News.GetPageOfNews(30)
select new XElement("item",
new XElement("title", item.Title),
new XElement("description", item.Summary),
new XElement("link", String.Format("http://coasterbuzz.com/News/{0}/{1}.aspx",
item.NewsID, item.UrlName)),
new XElement("pubDate", item.PostDate.Value.ToString("R"))
)
You can see that the actual creation of the element looks just like our previous example, but what makes it spicy is the LINQ query that precedes it. News.GetPageOfNews(30) is a static function in my class library that returns a List<News> object, a generic list of News objects. In English, these lines say, "Get items from the function, and select XElement objects composed of values from each item." You can use any enumerable collection here. I have these News objects with properties like Title and Summary. The output of this block looks something like this:
<item>
<title>Georgia Aquarium beluga whale gravely ill</title>
<description>Condition is not getting worse, but whale remains very weak.</description>
<link>http://coasterbuzz.com/News/9134/georgia-aquarium-beluga-whale-gravely-ill.aspx</link>
<pubDate>Fri, 29 Dec 2006 14:47:23 GMT</pubDate>
</item>
<item>
<title>Disney closes gates of Magic Kingdom and Disney-MGM at capacity</title>
<description>Holiday crowds force temporary closures.</description>
<link>http://coasterbuzz.com/News/9133/disney-closes-gates-of-magic-kingdom-and-disney-mgm-at-capacity.aspx</link>
<pubDate>Fri, 29 Dec 2006 14:47:21 GMT</pubDate>
</item>
<item>
<title>Joyland ownership: Deal closes in January</title>
<description>New owner says he will not buy Libertyland.</description>
<link>http://coasterbuzz.com/News/9132/joyland-ownership-deal-closes-in-january.aspx</link>
<pubDate>Thu, 28 Dec 2006 14:18:02 GMT</pubDate>
</item>
The "old way" of doing this probably meant using a foreach loop and in each loop building the set of objects. This arrangement using LINQ is a lot cleaner and easier to understand.
One of the neat things about LINQ to XML is that it doesn't always require you to work in complete documents or streams. You can work in fragments that correspond to the objects you're creating. That said, XDocument and XElement are almost the same, except that XDocument will generate the XML declaration and a document type. That's an important footnote when the data you're wrangling is intended to be put out into the world for consumption by other software, like an RSS feed.
This is probably the most simple use of LINQ, and specifically LINQ to SQL, that I can think of, and there is a lot more to learn.
©2013, POP World Media, LLC. All rights reserved
Legal, privacy, terms of service