Syndicating RSS in an ASP.Net MVC app using WCF

Tuesday, December 15, 2009

About a month back Rob Conery mentioned that he was going to create a blogging application using MVC, which caused me to think to myself: “SELF! why don’t you try something like that and see what you can come up with?”  So in my spare time when I am not doing real work or at home shuttling kids around, I’ve been playing around with this idea.  I am really happy with my BlogEngine.Net set up and probably will continue to use it even if I were to finish this app but I figure this would be an opportunity to just do something different than I have been doing lately and learn some new things that I have not had a chance to work with as of yet.

So with that in mind, one of the first things I decided to tackle was going about syndicating an RSS feed and as it turns out it is not too terribly difficult to do; although, there are some things I did struggle with—uhhh---but I am getting ahead of myself.

 

Build you own ActionResult

I mention in my previous post that ASP.Net come with several different controller actions, and the one that I used in the that example was the JsonResult.  Well JsonResult really is just a class that is derived from the abstract class ActionResult.  I can write my own class that derives from ActionResult to return back to the view an RSS feed which is what I am going to do in this example.

 

   1: using System.ServiceModel.Syndication;
   2: using System.Web.Mvc;
   3: using System.Xml;
   4:  
   5: namespace Aviblog.Core.ActionResults
   6: {
   7:     public class RssResult : ActionResult
   8:     {
   9:         private readonly SyndicationFeed _feed;
  10:  
  11:         public RssResult(SyndicationFeed feed)
  12:         {
  13:             _feed = feed;
  14:         }
  15:  
  16:         public override void ExecuteResult(ControllerContext context)
  17:         {
  18:             context.HttpContext.Response.ContentType = "application/rss+xml";
  19:             var formatter = new Rss20FeedFormatter(_feed);
  20:  
  21:             using (XmlWriter writer = XmlWriter.Create(context.HttpContext.Response.Output))
  22:                 if (writer != null) formatter.WriteTo(writer);
  23:         }
  24:     }
  25: }

So in my custom RssResult that inherits from ActionResult, I need to then override the ExecuteResult method.  Inside that method I then need to set the response type to “application/rss+xml” so it can be rendered as RSS.  The next thing to notice is that in my constructor I am passing in an object of type SyndicationFeed which is a part of WCF.  It is in the System.ServiceModel.Syndication namespace.  You can find it by referencing the System.ServiceModel.Web assembly.  I can then use the Rss20FeedFormatter to generate XML which is RSS compliant.

Building the SyndicationFeed

Building the SyndicationFeed object is where I had the most trouble. If you just want a simple compliant RSS feed, this guy works great; however, as you can see just from looking at my own RSS feed there many other elements you can extend to an RSS feed and adding those to the SyndicationFeed element is a bit tricky.

   1: public SyndicationFeed Buildfeed()
   2:         {
   3:             var settingQry = _settingsRepository.GetSettings();
   4:             const string nameKey = "name";
   5:             const string nameDescription = "description";
   6:             const string link = "link";
   7:             string blogTitle = _settingsService.GetValue(settingQry, nameKey);
   8:             string blogDescription = _settingsService.GetValue(settingQry, nameDescription);
   9:             Uri blogUrl = _settingsService.GetUri(settingQry, link);
  10:  
  11:             var feed = new SyndicationFeed(blogTitle, blogDescription, blogUrl);
  12:  
  13:             foreach (var element in _feedRepository.GetFeedElements())
  14:                 feed.ElementExtensions.Add(BuildXmlElement(element));
  15:  
  16:             feed.Items = LoadPosts(_postService.GetMostRecentPosts());
  17:             return feed;
  18:         }
  19:  
  20:         private static XmlElement BuildXmlElement(FeedElementDto element)
  21:         {
  22:             var doc = new XmlDocument();
  23:             XmlElement feedElement = doc.CreateElement(element.Prefix, element.Element, element.Namespace);
  24:             feedElement.InnerText = element.InnerText;
  25:             return feedElement;
  26:         }

 

From the code you can see that when I am instantiating the SyndicationFeed object I am passing in the title of the blog, the blog description, and the URL.  Where it got a bit hairy, was adding other elements that prefixed a common namespace.  I could add the namespace for each element (as seen in the code) but there does not seem to be a way to put the common namespaces in say the channel element and then just use the prefix to those namespaces later. I ended up having to repeat the same namespaces over again.  But that being said, it is still pretty easy to build the feed.

Once the feed object is ready I then could add each of the posts by mapping them to a SyndicationItem object and then add those collection of objects to the items collection in the feed.

   1: private static IEnumerable<SyndicationItem> LoadPosts(IEnumerable<PostDto> posts)
   2:         {
   3:             IList<SyndicationItem> items = new List<SyndicationItem>();
   4:             foreach (var post in posts)
   5:             {
   6:                 var item = new SyndicationItem();
   7:                 item.Title = new TextSyndicationContent(post.Title);
   8:                 item.Authors.Add(new SyndicationPerson(post.Author));
   9:                 item.Content = SyndicationContent.CreateHtmlContent(post.PostContent);
  10:                 items.Add(item);
  11:             }
  12:             return items;
  13:         }

 

 

 

The Controller Class

Now that I have my RssResult and my functionality to build the SyndicationFeed object, I can then create an action inside my controller class that puts the two together.

   1: public class SyndicationController : Controller
   2:     {
   3:         private readonly IRss20SyndicationService _rss20SyndicationService;
   4:  
   5:         public SyndicationController(IRss20SyndicationService rss20SyndicationService)
   6:         {
   7:             _rss20SyndicationService = rss20SyndicationService;
   8:         }
   9:  
  10:         public RssResult Rss()
  11:         {
  12:  
  13:             return new RssResult(_rss20SyndicationService.Buildfeed());
  14:         }
  15:     }

 

 

The Result

So when I browse to domain/controller/action in this case http://localhost:64533/Syndication/Rss I get the following RSS sample:

 

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
   3:   <channel>
   4:     <title>Steve Moseley</title>
   5:     <link>http://www.test.com/</link>
   6:     <description>Trying my best to apprehend all these dev changes.</description>
   7:     <blogChannel:blogRoll xmlns:blogChannel="http://backend.userland.com/blogChannelModule">http://www.avingtonsolutions.com/blog/opml.axd</blogChannel:blogRoll>
   8:     <blogChannel:blink xmlns:blogChannel="http://backend.userland.com/blogChannelModule">http://www.dotnetblogengine.net/syndication.axd</blogChannel:blink>
   9:     <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Steve Moseley</dc:creator>
  10:     <dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Steve Moseley</dc:title>
  11:     <geo:lat xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">0.000000</geo:lat>
  12:     <geo:long xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">0.000000</geo:long>
  13:     <item>
  14:       <author>Steve</author>
  15:       <title />
  16:       <description>
  17:        blah, blah, blah
  18:       </description>
  19:     </item>
  20:   </channel>
  21: </rss>

 

Looks pretty good, although, if you look at my actual RSS feed and this one, you will notice that the name spaces are repeated verses being listed at the top as they probably should be.  I need to play around with it some more but this gets the job done and I did not have to write all that much code, which is nice.

comments powered by Disqus