Building an RSS feed using LINQ to XML and LINQ to SQL
Version : VS 2008 Beta 2
In this post, I am going to show you how to build a RSS feed of the employees in the NorthWind database using LINQ. Before you proceed, you may want to read Scott's introduction of LINQ to XML over here if you haven't done so already.
To start, lets look at how we can programmatically create the XML document below:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <Employees>
3: <Employee>
4: <Name>Tom</Name>
5: <ID>1</ID>
6: </Employee>
7: <Employee>
8: <Name>Jane</Name>
9: <ID>2</ID>
10: </Employee>
11: </Employees>
Without LINQ, we could use the XmlDocument class to create an in-memory tree representation of the document above and write it to the HttpResponse stream like so:
1: XmlDocument doc = new XmlDocument();
2:
3: XmlDeclaration xmldec = doc.CreateXmlDeclaration("1.0", "utf-8", null);
4: XmlElement root = doc.DocumentElement;
5: doc.InsertBefore(xmldec, root);
6:
7: XmlElement employees = doc.CreateElement("Employees");
8: doc.AppendChild(employees);
9:
10: XmlElement employee1 = doc.CreateElement("Employee");
11: employees.AppendChild(employee1);
12:
13: XmlElement empID1 = doc.CreateElement("ID");
14: empID1.InnerText = "1";
15: employee1.AppendChild(empID1);
16:
17: XmlElement empName1 = doc.CreateElement("Name");
18: empName1.InnerText = "Tom";
19: employee1.AppendChild(empName1);
20:
21: XmlElement employee2 = doc.CreateElement("Employee");
22: employees.AppendChild(employee2);
23:
24: XmlElement empID2 = doc.CreateElement("ID");
25: empID1.InnerText = "2";
26: employee1.AppendChild(empID2);
27:
28: XmlElement empName2 = doc.CreateElement("Name");
29: empName2.InnerText = "Jane";
30: employee2.AppendChild(empName2);
31:
32: Response.Clear();
33: doc.Save(Response.Output);
34: Response.End();
LINQ provides a easier and cleaner way to create XML documents known as functional construction. Using functional construction, you can create all or parts of your in-memory XML tree in a single statement. This is made possible by a constructor in the XElement and XDocument classes that take in a params object (FYI, XElement and XDocument both inherit from type XContainer).
1: public XElement(XName name, params object[] content);
2: public XDocument(XDeclaration declaration, params object[] content);
The constructor calls an internal method called AddContentSkipNotify to validate the type of object passed into the params object. From the documentation, parameters can be any of the following:
- A string, which is added as text content. This is the recommended pattern to add a string as the value of an element; the LINQ to XML implementation will create the internal XText node.
- An XText, which can have either a string or CData value, added as child content. This is mainly useful for CData values; using a string is simpler for ordinary string values.
- An XElement, which is added as a child element
- An XAttribute, which is added as an attribute
- An XProcessingInstruction or XComment, which is added as child content
- An IEnumerable, which is enumerated, and these rules are applied recursively
- Anything else, ToString() is called and the result is added as text content
- null, which is ignored
Using functional construction, we can create our in memory XML tree and write it to the response stream like so:
1: XDocument doc = new XDocument(
2: new XDeclaration("2.0", "utf-8", "yes"),
3: new XElement("Employees",
4: new XElement("Employee",
5: new XElement("Name", "Tom"),
6: new XElement("ID", "1")),
7: new XElement("Employee",
8: new XElement("Name", "Tom"),
9: new XElement("ID", "1"))));
10:
11: Response.Clear();
12: doc.Save(Response.Output);
13: Response.End();
This code is easier to write compared to the XmlDocument method. It is also easy see the underlying structure of the XML document by looking at the code.
From the documentation above, we know that if we pass in IEnumerable<T> to the XElement constructor, it will get enumerated and the rules will be applied recursively to each element in the collection.
With this knowledge, we could rewrite the code above with a LINQ TO XML query that returns an IEnumerable<XElement> from a collection of anonymous types like so:
1: var employees = new[] {
2: new {Name = "Tom", ID = 1},
3: new {Name = "Jane", ID = 2}
4: };
5:
6: XDocument doc = new XDocument(
7: new XDeclaration("2.0", "utf-8", "yes"),
8: new XElement("Employees",
9: //LINQ query that returns IEnumerable<XElement>
10: from e in employees
11: select new XElement("Employee",
12: new XElement("Name", e.Name),
13: new XElement("ID", e.ID)
14: )
15: )
16: );
17:
18: Response.Clear();
19: doc.Save(Response.Output);
20: Response.End();
Note that "var employees" above is a collection of anonymous types. The output of this code will produce the same XML document like the previous two code samples did.
Instead of the hard coded employee collection, we could use a LINQ to SQL query that returns an IEnumerable<XElement> of employee names and their description from the database. That is the basic idea behind this post - Using LINQ to SQL to query the database, construct an in-memory XML tree using LINQ to XML by following the RSS specification and finally writing it out to the HttpResponse stream.
The XDocument we are going to construct will follow the RSS 2.0 specification (the minimum required) below:
1: <?xml version="1.0" encoding="utf-8" standalone="yes"?>
2: <rss version="2.0">
3: <channel>
4: <title></title>
5: <link></link>
6: <description></description>
7: <item>
8: <title></title>
9: <description></description>
10: <link></link>
11: </item>
12: ...
13: ...
14: ...
15: <item>
16: <title></title>
17: <description></description>
18: <link></link>
19: </item>
20: </channel>
21: </rss>
Assuming we have created our Nortwind LINQ to SQL object model, we can write the final code in 25 lines(!) like this:
1: protected void Page_Load(object sender, EventArgs e)
2: {
3: NorthwindDataContext context = new NorthwindDataContext();
4: Response.Clear();
5: Response.ContentType = "text/xml";
6:
7: XDocument document = new XDocument(
8: new XDeclaration("1.0", "utf-8", null),
9: new XElement("rss",
10: new XElement("channel",
11: new XElement("title", "Employees of Northwind Traders Inc."),
12: new XElement("link", "http://www.northwindtraders.com"),
13: new XElement("description", "Employees of Northwind Traders Inc. ordered by last name."),
14: from emp in context.Employees
15: orderby emp.LastName
16: select new XElement("item",
17: new XElement("title", emp.LastName + ", " + emp.FirstName),
18: new XElement("description", emp.Notes),
19: new XElement("link", "http://www.northwindtraders.com/employees.aspx?id=" + emp.EmployeeID)
20: )
21: ),
22: new XAttribute("version", "2.0")));
23: document.Save(Response.Output);
24: Response.End();
25: }
Note that the LINQ to SQL query fetches the employee name (LastName + FirstName columns) and the Notes column and creates an XElement called "title" and "description" respectively. We also create a dummy "link" node that points to www.northwindtraders.com since it is part of the requirement of the RSS specs. Once we have our XDocument created, we clear the Response stream, set the content type to text/xml and then "Save" the document to the HttpResponse stream.
This is not the optimal solution though since the DB is queried everytime the page is requested. We obviously have to cache the results on the server for a set amount of time. This can easily be done by calling the .ToList() extension method on the XDocument and storing the results in Cache. I show an example of how to cache the results of a LINQ query here
Note also that you should always HtmlEncode data before rendering it on the browser (which is not shown in the sample code).
I hope this post has shown you how super easy it is to create XML documents from content in the database using LINQ. To learn more, download VS 2008 Beta 2 and check out the LINQ samples.