To help get more familiar with the new XML Programming API that is hidden away inside of LINQ to XML I recently set on a journey to update the code that creates my RSS feed to use LINQ to XML. In order to complete my journey I needed to get familiar with how to use functional construction to build XML from a set of in memory objects. Before diving into the LINQ to XML code let's first take a peak at the old code which was building the XML using an XmlWriter.
1 StringWriter sw = new StringWriter();
2 XmlTextWriter xml = new XmlTextWriter(sw);
3 xml.WriteProcessingInstruction("xml-stylesheet", "href=\"" + GetRootUrl() + "/friendly-rss.xsl\" type=\"text/xsl\" media=\"screen\"");
4 xml.WriteStartElement("rss");
5 xml.WriteAttributeString("version", "2.0");
6 xml.WriteAttributeString("xmlns:dc", "http://purl.org/dc/elements/1.1/");
7 xml.WriteAttributeString("xmlns:slash", "http://purl.org/rss/1.0/modules/slash/");
8 xml.WriteAttributeString("xmlns:wfw", "http://wellformedweb.org/CommentAPI/");
9 xml.WriteStartElement("channel");
10 xml.WriteElementString("title", channel.DisplayName));
11 xml.WriteElementString("link", GetRootUrl() + "/" + channel.Url);
12 xml.WriteElementString("generator", "ActiveType CMS v0.1");
13 xml.WriteElementString("dc:language", "en-US");
14 xml.WriteElementString("description", channel.Description);
15
16 foreach(Posting posting in postings) {
17 xml.WriteStartElement("item");
18 xml.WriteElementString("dc:creator", posting.ActiveRevision.LastModifiedBy.FullName);
19 xml.WriteElementString("title", posting.ActiveRevision.DisplayName);
20 xml.WriteElementString("link", postLink);
21 xml.WriteElementString("pubDate", posting.ActiveRevision.CreatedDate.ToString("r"));
22 xml.WriteElementString("guid", postLink);
23 xml.WriteElementString("comments", postLink + "#comments");
24 xml.WriteElementString("wfw:commentRss", postLink + "/commentRss.aspx");
25 xml.WriteElementString("slash:comments", posting.NumberOfComments.ToString());
26 WriteDescriptionElement(xml, true, posting.ActiveRevision.AllowContentFiltering, posting.ActiveRevision.Content, shareLinks);
27 xml.WriteEndElement();
28 }
29
30 xml.WriteEndElement();
31 xml.WriteEndElement();
32 xml.Close();
I've re-organized the code for this post but most of the basics have stayed intact. As you can see we use an XmlWriter (XmlTextWriter to be more specific) to write our root rss element, our main channel, as well as the 25 most recent postings. The LINQ to XML code looks somewhat similar.
1 XNamespace dc = "http://purl.org/dc/elements/1.1/";
2 XNamespace slash = "http://purl.org/rss/1.0/modules/slash/";
3 XNamespace wfw = "http://wellformedweb.org/CommentAPI/";
4
5 XDocument rss = new XDocument(
6 new XProcessingInstruction("xml-stylesheet", "href=\"" + GetRootUrl() + "/friendly-rss.xsl\" type=\"text/xsl\" media=\"screen\""),
7 new XElement("rss", new XAttribute("version", "2.0"),
8 new XElement("channel",
9 new XElement("title", channel.DisplayName)),
10 new XElement("link", GetRootUrl() + "/" + channel.Url),
11 new XElement("generator", "ActiveType CMS v0.1"),
12 new XElement(dc + "language", "en-US"),
13 new XElement("description", GetChannelDescription(channel)),
14
15 from p in postings.OfType<Posting>()
16 where p.ActiveRevision.IsSyndicated && p.ActiveRevision.Content.Length > 0
17 select new XElement("item",
18 new XElement(dc + "creator", p.ActiveRevision.LastModifiedBy.FullName),
19 new XElement("title", p.ActiveRevision.DisplayName),
20 new XElement("link", GetLink(p)),
21 new XElement("pubDate", p.ActiveRevision.CreatedDate.ToString("r")),
22 new XElement("guid", GetLink(p)),
23 new XElement("comments", GetLink(p) + "#comments"),
24 new XElement(wfw + "commentRss", GetLink(p) + "/commentRss.aspx"),
25 new XElement(slash + "comments", p.NumberOfComments.ToString()),
26 GetDescription(p)
27 )
28 )
29 )
30 );
The main difference is that we use functional construction to build our XML using a single statement (albeit spread across many lines) rather then writing each element/attribute using the XmlWriter. It should also be noted that the sample code using the XmlWriter had to be simplified a good bit in order to fit within this post and be readable. The LINQ to XML code hasn't been changed at all (well mostly). The above code shows a couple things which should be pointed out.
- To create elements that use namespaces we create an XNamespace and append the XNamespace to the local name for the element. Notice that the XNamespace has an implicit conversion from a string and can be added to the local name to create an XName. (new XElement(dc + "language", "en-US"))
- Notice that we can create XElements using either hard coded strings or via method calls.
- Since our query (line 15-16) returns an IEnumerable<XElement> and the XElement constructor is capable of taking an IEnumerable<XElement> and adding each item as a child element we can directly embed a query in the middle of our functionally constructed document.
- Even though we're using old school collections in this code (postings is a custom collection that inherits from ArrayList) we can use the collection as the basis of our query by way of the OfType<T>() extension method. The OfType extension method converts an old school collection into an IEnumerable<T> which makes it possible to use it in the from clause of our query.
- The resulting XML will not have xml namespace prefixes. In order to get prefixes output we need to do some other work which I wasn't up for just yet. Rather then outputting <dc:language>en-US</dc:language> we'll get <language xmlns="http://purl.org/dc/elements/1.1/">en-US</language>. I'll talk more about namespaces and namespaces prefixes in another post so if your interested in how to get LINQ to XML to serialize your XML using prefixes stay tuned.
- In addition to adding elements by newing up XElements we can also create a function that returns an XElement and pass that to our constructor and get it added as a child element (line 26).
In future posts I'll dive into some more details about how LINQ to XML improves upon the imperative approach provided by the DOM when creating Xml from scratch, overview how LINQ to XML handles namespaces and namespaces prefixes, as well as introduce some of the key concepts and design goals of LINQ to XML.
tags: linq, xlinq, linqtoxml, xml