RSS Feeds and Google Sitemaps for ASP.NET MVC with LINQ To XML

4.33 (6 votes)

For the majority of personal web sites, two uses of XML are commonplace: creating an RSS feed and a Google Sitemap. Here, I look at how to create both of those using LINQ to XML for an ASP.NET MVC web site.

I've already looked at both RSS Feeds and Google Sitemaps before, using an XMLTextWriter object to generate the output, and it can be a fairly laborious task. LINQ to XML requires far less code to achieve the same thing. Both of the documents will be generated on demand - that is as a result of someone requesting the appropriate URL. This is so that the documents incorporate the most up-to-date content as articles are added and amended regularly. The content will be drawn from a database and the Entity Framework is the mechanism I have chosen to act as my data access technology.

My full blown Article class consists of the text of the article and a lot of meta-data that just isn't necessary for RSS or a sitemap, and I have already created a couple of classes to cater for the retrieval of a small subset of Article data, so I shall use those. The actual data retrieval is in a class called ArticleRepository, which is in the Model area of the application. Starting with the RSS feed, I have created a method that lists the most recent 20 items that have been added:

public IEnumerable<ArticleSummary> GetRSSFeed()
  return (de.ArticleSet
              .OrderByDescending(a => a.DateCreated)
              .Select(a => new ArticleSummary
                                ID = a.ArticleID,
                                Head = a.Headline,
                                Intro = a.Abstract,
                                CreatedDate = a.DateCreated

I also need to create a Controller to handle this (and the sitemap), so I shall call it XMLController:

using System;
using System.Linq;
using System.Web.Mvc;
using System.Xml.Linq;
using MikesDotnetting.Helpers;
using MikesDotnetting.Models;

namespace MikesDotnetting.Controllers
  public class XMLController : Controller
    private IArticleRepository repository;

    public XMLController() : this(new ArticleRepository())


    public XMLController(IArticleRepository rep)
      repository = rep;


This controller is making use of the Repository pattern, in that instead of invoking the ArticleRepository and calling it directly, I am programming against an interface instead. If I decide to get fed up with Entity Framework, and choose to use LINQ to SQL, or even ADO.NET code for data access, I will only have to change this class in one place, rather than have to go through every method and unhook the concrete ArticleRepository class from them. So now I need to add a method to generate the RSS feed, which is actually just a streamed XML document. We will look at the code that does that, then examine it:

public ContentResult RSS()
  const string url = "{0}/{1}";
  var items = repository.GetRSSFeed();
  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", "Mikesdotnetting News Feed"),
          new XElement("link", ""),
          new XElement("description", "Latest additions to Mikesdotnetting"),
          new XElement("copyright", "(c)" + DateTime.Now.Year + ", Mikesdotnetting. All rights reserved"),
        from item in items
        new XElement("item",
          new XElement("title", item.Head),
          new XElement("description", item.Intro),
          new XElement("link", String.Format(url, item.ID, UrlTidy.ToCleanUrl(item.Head))),
          new XElement("pubDate", item.CreatedDate.ToString("R"))

  return Content(rss.ToString(), "text/xml");

If you compare LINQ to XML with using the XmlTextWriter, you can see that a lot less code is required. The savings mainly derive from not having to explicitly close elements in the document, and not having to write out the individual parts of an element using different methods such as WriteElementString(), WriteStartElement() etc. Not only that, but you can almost see the outline of the finished XML document from the code. The link node within the item elements has an odd method applied to the item.Head to create a link. UrlTidy.ToCleanUrl takes the existing article title, and replaces spaces with dashes etc to give a clean SEO-friendly url. The code for the method is available in this previous article (first block of code). The result of the action is returned as a ContentResult, which allows for any type of data. In this case, the content type is also specified as text/xml. I have seen some examples of RSS feed that use application/rss+xml. This works for a large number of rss readers, but cannot be relied upon all the time - application/rss+xml is NOT a standard MIME type.

Now to the second action on the controller:

public ContentResult Sitemap()
  XNamespace ns = "";
  const string url = "{0}/{1}";
  var items = repository.GetAllArticleTitles();
  var sitemap = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
      new XElement(ns + "urlset",
          from item in items
          new XElement("url",
            new XElement("loc", string.Format(url, item.ID, UrlTidy.ToCleanUrl(item.Head))),
            item.DateAmended != null ?
                new XElement("lastmod", String.Format("{0:yyyy-MM-dd}", item.DateAmended)) :
                new XElement("lastmod", String.Format("{0:yyyy-MM-dd}", item.DateCreated)),
            new XElement("changefreq", "monthly"),
            new XElement("priority", "0.5")
  return Content(sitemap.ToString(), "text/xml");

The main differrence between this method and the previous one is the presence of a namespace in the XML. The RSS sepcification doesn't require one to be present, but the Sitemap 0.9 specification does:

<urlset xmlns="">

Lots of developers see a namespace in XML and treat it in the same way as attributes, with code similar to this:

string ns = "";

var sitemap = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
    new XElement("urlset",new XAttribute("xmlns", ns),

And then they see an exception message along these lines:

The prefix '' cannot be redefined from '' to '' within the same start element tag.

Namespaces are different to attributes, and need to be defined as an XNamespace object before being passed in to the document.

Now that the actions have been created, all that is needed is to register a couple of routes to point to them:

  "Sitemap", "sitemap",
  new { controller = "XML", action = "Sitemap" });
  "RSS", "rss",
  new{controller="XML", action="RSS"});

And we are done.

There are some improvements that can be made such as use of caching or perhaps even generating a file that sits on disk. This will save having the sitemap or rss feed being generated anew unnecessarily when there have been no modifications to it since the last request. Also, building on the comments from Jim Wooley and John Sheehan below, the approach I have taken does in fact technically result in the View being built within the Controller action, which is OK if you have a simple requirement. You could almost see LINQ to XML as acting as an Html.Helper extension method in this case (which you would usually find in Views). However, as Jim points out, passing a strongly typed collection to a View and then formatting the results there is a much better way of doing things if you intend to expose multiple feeds. John's approach using the Argotic Syndication Framework to build a custom ViewResult is particularly sweet, and, like mine, means there is no View in the project.

Date Posted:
Last Updated:
Posted by:
Total Views to date: 46769


- Jim Wooley

Correct me if I'm wrong, but it appears that you are formatting your VIEW in your Controller. If you were to create a common RSS view, your controller could project from the actual data repository into a structure that the RSS can consume. In your case, you could project into an IEnumerable<SyndicationFeedITem> and then consume that in your View. This is particularly helpful if you plan to expose different kinds of RSS feeds (filtered by category, comments, etc).

Another recommendation regarding the namespace issue. If you use XNamespace ns = XNamespace.GetNamespace(""); then you could use the overloaded + operator to combine the namespace with the name as follows: new XAttribute(ns + "xml).

- Mike


No you are not wrong. I am definitely formatting the View in the controller. But your point about the potential shortcomings of this approach is well made.

- John Sheehan

This would be a great situation for a custom ActionResult. As an example I created a RssResult that takes an Argotic feed object and handles spitting out the proper content type, etc. It's a little more reusable than specifying the params in a ContentResult every time. Example here:

- Mike


I like that approach, John. I wasn't aware of Argotic, but I'm playing with it now.


- Pete

Was just wondering how I go about pulling in feeds from multiple unspecified sites by keyword rather than from a specific site.

- Mike


This article is about generating feeds, not consuming them.

- Huey

I got this error message in Sitemap action. What's wrong with it?

Exception Details: System.NotSupportedException: Only parameterless constructors and initializers are supported in LINQ to Entities.

Line 65: var sitemap = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),

- Mike


I'm not an expert on LINQ to XML or LINQ to Entities, but all I do know is that some of the error messages are completely useless, obscure and will send you on a wild goose chase. Probably, further down the code, you have forgotten a comma or something similar.

Try closing the document off in code as early as you can and comment out the remainder. Then walk your closure down the code, each time adding a bit more to the document until you get the error again. Whichever bit you have just added will contain the cause of the problem.

- Ryan

The timing of this post showing up on the ASP.NET site is comical to me - I saw it literally 2 minutes after publishing an xml sitemap project to codeplex:

I think this is a great approach for simple sitemaps involving one controller and a known path, however, the code could get rather ugly for anything more than that.

My project uses an "opt in" approach whereby a developer can include actions in the sitemap by decorating them with a [Sitemap] attribute. My blog post here describes how to use it:

I'd appreciate any feedback you may have if you decide to check it out.


- Mike


My feedback is that you should submit your post to the ASP.NET site. Nice work!

- Jeremy

Thanks for the article Mike.

I've set this up on my site, but Google is detecting the following error.

Incorrect namespace
Your Sitemap or Sitemap index file doesn't properly declare the namespace.

Parent tag: urlset
Tag: url

Did / Do you have the same problem? If so how did you solve it?


- Jeremy

Here's the fix:
You need to add the XNamespace to every XElement.

public ContentResult Sitemap()
XNamespace ns = "";
const string url = "{0}/{1}";
var items = repository.GetAllArticleTitles();
var sitemap = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
new XElement(ns + "urlset",
from item in items
//Add ns to every element.
new XElement(ns + "url",
new XElement(ns + "loc", string.Format(url, item.ID,
item.DateAmended != null ?
new XElement(ns + "lastmod", String.Format("{0:yyyy-MM-dd}",
tem.DateAmended)) :
new XElement(ns + "lastmod", String.Format("{0:yyyy-MM-dd}", item.DateCreated)),
new XElement(ns + "changefreq", "monthly"),
new XElement(ns + "priority", "0.5")
return Content(sitemap.ToString(), "text/xml");

Recent Comments

Praveen 12/02/2016 14:22
In response to Migrating Classic ASP To ASP.NET Razor Web Pages Part One- Razor Syntax And Visual Basic
Nicely written article, just what I wanted to get me started, I am going to start working on a this...

Whitney W. 11/02/2016 15:37
In response to Adding A Controller
I am really new to everything and just started programming. I really need help in my project since I...

Fredrik 11/02/2016 13:10
In response to Request.Form Is Empty When Posting To ASPX Page
It worked. Thank you!...

David Valdez 11/02/2016 03:08
In response to Reading Excel Files Without Saving To Disk In ASP.NET
Muchas, muchas, gracias. Thank you so much from Dominican Republic....

Zahid 10/02/2016 00:42
In response to How To Send Email In ASP.NET MVC
Hello Sir, Great post. Just a quick question, is it possible if we can ask a client to save in an...

Al Wilton 07/02/2016 03:11
In response to Windows Authentication With ASP.NET Web Pages
I've been using this advise for quite a while. Today I was setting up a .NET 4.6.1 site and it drove...

Anders 06/02/2016 15:38
In response to iTextSharp - Working with Fonts
Thanks a lot for this excellent series on iTextSharp....

J_R 06/02/2016 02:45
In response to Simple File Download Protection with ASP.NET
Mike, Thank you for taking the time to write this. It really helped me - though I could not out a...

Nemat 04/02/2016 16:24
In response to Solved - The Microsoft.ACE.OLEDB.12.0 provider is not registered on the local machine
Installing Microsoft Access Database Engine 2010 64 bit helped me. Thanks A lot!...

Bill Barbour 03/02/2016 18:34
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Wonderful example. I have it all working. I would like to add the image to each row of the index you...