With this implementation you can re-use parts of your content Controller (the database query for example) and render it on a RSS and/or ATOM feed.
Let's say you have an action like this (extracted from DotNetBurner’s source):
[AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
public ActionResult Upcoming (int? page, string sort)
{
var viewData = GetStoryListViewData<StoryListByCategoryData> (page);
int sortDays = GetSortDays (sort);
viewData.Stories = Repository.GetUpcomingStories (CurrentUserId, sortDays, SiteSettings.Settings.UpcomingMaxDays,
CalculateStartIndex (page), StorySettings.Settings.StoryPerPage);
viewData.Category = Strings.Upcoming;
viewData.CategoryDisplayName = Strings.Upcoming;
viewData.SortDays = sortDays;
return View ("Upcoming", viewData);
}
This action returns the upcoming stories. To turn it on a feed, i have just implement this new action:
[AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter, OutputCache (CacheProfile = "Feeds")]
public ActionResult UpcomingFeed(string sort, string feedType, int? feedCount)
{
return Content(Feed("Upcoming", null, sort, feedType, feedCount), "application/xml", Encoding.UTF8);
}
string Feed(string feed, int? id, string sort, string feedType, int? feedCount)
{
int sortDays = GetSortDays(sort);
if (!id.HasValue)
id = 0;
if (!feedCount.HasValue || feedCount == 0)
feedCount = StorySettings.Settings.StoryPerFeed;
if (feedCount > 200)
feedCount = 200;
IEnumerable<Story> stories = null;
var categories = Repository.GetCategories();
stories = Repository.GetUpcomingStories (CurrentUserId, sortDays, SiteSettings.Settings.UpcomingMaxDays, 1, (int)feedCount);
FeedType type = ("rss".Equals(feedType, StringComparison.InvariantCultureIgnoreCase)) ? FeedType.RSS20 : FeedType.ATOM10;
using (var memoryStream = new MemoryStream ())
{
FeedHelper.GenerateFeed (stories, categories, memoryStream, type, Url);
return Encoding.UTF8.GetString (memoryStream.ToArray ());
}
}
The Feed method is simplified here for the sake of the example, but it can take a “feed” parameter to return different feeds (for categories, tags, etc.). To generate the feed, i have implemented a FeedHelper class (you probably will have to tune it for your needs):
public enum FeedType
{
RSS20,
ATOM10
}
public static class FeedHelper
{
public static void GenerateFeed(IEnumerable<Story> stories,
IEnumerable<Category> categories,
Stream output,
FeedType type,
UrlHelper urlHelper)
{
// Create an XmlWriter to write the feed into it
using (XmlWriter writer = XmlWriter.Create(output))
{
// Set the feed properties
SyndicationFeed feed = new SyndicationFeed
(SiteSettings.Settings.Name,
SiteSettings.Settings.Title,
new Uri(SiteSettings.Settings.Address));
feed.LastUpdatedTime = DateTime.UtcNow;
feed.Language = SiteSettings.Settings.Language;
feed.Authors.Add(new SyndicationPerson(SiteSettings.Settings.AdminMail,
SiteSettings.Settings.Name,
SiteSettings.Settings.Address));
// Add categories
foreach (var category in categories)
foreach(var subcat in category.SubCategories)
feed.Categories.Add(new SyndicationCategory(subcat.Name));
// Set generator
feed.Generator = SiteSettings.Settings.Title;
// Set language
feed.Language = SiteSettings.Settings.Language;
string siteUrl = SiteSettings.Settings.Address;
// Add post items
List<SyndicationItem> items = new List<syndicationitem>();
foreach (var story in stories)
{
string url = String.Concat(siteUrl,
urlHelper.RouteUrl("Detail",
new RouteValueDictionary
{
{"id", story.StoryId},
{"title", story.Title.ConvertToUrlPath()}
}));
string voteButton = "{0}<br>".FormatWith(SwissKnife.GetVoteButtonFor(story.Url));
TextSyndicationContent content =
SyndicationContent.CreateHtmlContent(String.Concat("<div><p>", story.Description, "</p>",
voteButton, "</div>"));
SyndicationItem item = new SyndicationItem(story.Title,
content,
new Uri(url),
url,
new DateTimeOffset(story.PostedOn));
item.PublishDate = story.PostedOn;
item.Categories.Add(new SyndicationCategory(story.CategoryName));
items.Add(item);
}
feed.Items = items;
// Write the feed to output
if (type == FeedType.RSS20)
{
Rss20FeedFormatter rssFormatter = new Rss20FeedFormatter(feed);
rssFormatter.WriteTo(writer);
}
else
{
Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(feed);
atomFormatter.WriteTo(writer);
}
writer.Flush();
}
}
}
This uses System.ServiceModel.Syndication to create a feed from the same database query that is used to render the site content. With this, i have enabled DotNetBurner to have feeds in less than 4 hours.
Drop me a comment if you implement this on a similar time :)