Creating RSS feeds in ASP.NET MVC

ASP.NET MVC is the technology that brought me to Microsoft and the west-coast and it’s been fun getting to grips with it these last few weeks.

Last week I needed to expose RSS feeds and checked out some examples online but was very disappointed.

If you find yourself contemplating writing code to solve technical problems rather than the specific business domain you work in you owe it to your employer and fellow developers to see what exists before churning out code to solve it.

The primary excuse (and I admit to using it myself) is “X is too bloated, I only need a subset. I can write that quicker than learn their solution.” but a quick reality check:

  • Time – code always takes longer than you think
  • Bloat – indicates the problem is more complex than you realize
  • Growth – todays requirements will grow tomorrow
  • Maintenance – fixing code outside your business domain
  • Isolation – nobody coming in will know your home-grown solution

The RSS examples I found had their own ‘feed’ and ‘items’ classes and implemented flaky XML rendering by themselves or as MVC view pages.

If these people had spent a little time doing some research they would have discovered .NET’s built in SyndicatedFeed and SyndicatedItem class for content and two classes (Rss20FeedFormatter and Atom10FeedFormatter )  to handle XML generation with correct encoding, formatting and optional fields.

All that is actually required is a small class to wire up these built-in classes to MVC.

using System;
using System.ServiceModel.Syndication;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Xml;

namespace MyApplication.Something
{
  public class FeedResult : ActionResult {
    public Encoding ContentEncoding { get; set; }
    public string ContentType { get; set; }

    private readonly SyndicationFeedFormatter feed;
    public SyndicationFeedFormatter Feed{
      get { return feed; }
    }

    public FeedResult(SyndicationFeedFormatter feed) {
      this.feed = feed;
    }

    public override void ExecuteResult(ControllerContext context) {
      if (context == null)
        throw new ArgumentNullException("context");

      var response = context.HttpContext.Response;
      response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/rss+xml";

      if (ContentEncoding != null)
        response.ContentEncoding = ContentEncoding;

      if (feed != null) {
        using (var xmlWriter = new XmlTextWriter(response.Output)) {
          xmlWriter.Formatting = Formatting.Indented;
          feed.WriteTo(xmlWriter);
        }
      }
    }
  }
}

In a controller that supplies RSS feed simply project your data onto SyndicationItems and create a SyndicationFeed then return a FeedResult with the FeedFormatter of your choice.

public ActionResult NewPosts() {
  var blog = data.Blogs.SingleOrDefault();
  var postItems = data.Posts.Where(p => p.Blog = blog)
    .OrderBy(p => p.PublishedDate)
    .Take(25)
    .Select(p => new SyndicationItem(p.Title, p.Content, new Uri(p.Url)));

  var feed = new SyndicationFeed(blog.Title, blog.Description, new Uri(blog.Url) , postItems) {
    Copyright = blog.Copyright,
    Language = "en-US"
  };

  return new FeedResult(new Rss20FeedFormatter(feed));
}

This also has a few additional advantages:

  1. Unit tests can ensure the ActionResult is a FeedResult
  2. Unit tests can examine the Feed property to examine results without parsing XML
  3. Switching to Atom format involved just changing the new Rss20FeedFormatter to Atom10FeedFormatter

[)amien

5 responses

  1. Avatar for Dan F

    That code makes me happy. I can't define good code, but I know it when I see it. Simple, extensible, testable, re-uses pre existing artwork that's baked into the framework, it ticks a whole lot of boxes. Nice work Mr Guard.

    Dan F 27 April 2010
  2. Avatar for Jarrod N

    Hi Damien, Thanks for the well written article, RSS is really the glue that holds a lot of services together, leveraging it is well worth while.

    On a side note, I was very happy to see your code using the "Envy Code R" font, I've switched to that recently and haven't looked back :)

    -Jarrod

    Jarrod N 28 April 2010
  3. Avatar for David Whitney

    I've been using a similar method for producing feeds for some time. I've found that you suffer if you start requiring CDATA.

    While my solution isn't quite ideal, this is the technique I use to get around the lack of CDATA support in the .NET SyndicationFeed objects.

    public class RssResult : ActionResult {
        public SyndicationFeed Feed { get; set; }
    
        public RssResult(SyndicationFeed feed) {
            Feed = feed;
        }
    
        public override void ExecuteResult(ControllerContext context) {
            context.HttpContext.Response.ContentType = "application/xml";
            var rssFormatter = new Rss20FeedFormatter(Feed);
            var settings = new XmlWriterSettings {
                NewLineHandling = NewLineHandling.None,
                Indent = true,
                Encoding = Encoding.UTF32,
                ConformanceLevel = ConformanceLevel.Document,
                OmitXmlDeclaration = true
            };
    
            var buffer = new StringBuilder();
            var cachedOutput = new StringWriter(buffer);
    
            using (var writer = XmlWriter.Create(cachedOutput, settings)) {
                if (writer != null) {
                    rssFormatter.WriteTo(writer);
                    writer.Close();
                }
            }
    
            var xmlDoc =  XDocument.Parse(buffer.ToString());
            foreach (var element in xmlDoc.Descendants("channel").First().Descendants("item").Descendants("description"))
                VerifyCdataHtmlEncoding(buffer, element);
    
            foreach (var element in xmlDoc.Descendants("channel").First().Descendants("description"))
                VerifyCdataHtmlEncoding(buffer, element);
    
            buffer.Replace(" xmlns:a10=\"http://www.w3.org/2005/Atom\"", " xmlns:atom=\"http://www.w3.org/2005/Atom\"");
            buffer.Replace("a10:", "atom:");
    
            context.HttpContext.Response.Output.Write(buffer.ToString());
        }
    
        private static void VerifyCdataHtmlEncoding(StringBuilder buffer, XElement element) {
            if(element.Value.Contains("")) {
                string cdataValue = string.Format("", element.Name, element.Value, element.Name);
                buffer.Replace(element.ToString(), cdataValue);
            }
        }
    }
    
    David Whitney 29 April 2010
  4. Avatar for David Whitney

    I just got a message regarding my above comment. Appears that the markup stripping in this blog has stripped out some of the code, which makes my comment appear insane!

    Here's a gist with the snippet that might make it make sense: https://gist.github.com/1027181

    The issue we were having was that elements were getting HTMLEncoded by default, ruining their entire purpose.

    David Whitney 15 June 2011
  5. Avatar for Judah Gabriel Himango

    Thanks! I used the RSS feed ActionResult in building MessianicChords.com. In particular, I used it to generate an RSS feed for user-uploaded content to the site. That feed is then consumed by IfThisThenThat, which publishes new RSS items to Twitter and Facebook. Pretty cool stuff.

    Thanks again for posting this RSS ActionResult, was very helpful.

    Judah Gabriel Himango 16 February 2012