RssResult – An ASP.NET MVC RSS ActionResult

Posted by William on Oct 22, 2009

One of the most common tasks for data serving websites is offering an RSS feed that users can subscribe to in order to keep up with the latest updates to that data.

Previously I posted the article Creating a simple RSS feed using ASP.NET MVC which demonstrated how to create an RSS feed in a quick, easy way. Here, I will show you how to create an RssResult to be returned directly from your controller. This offers a much more scalable, reusable approach that is flexible enough to handle any data that you wish to serve and is designed specifically for addition to your ASP.NET MVC stack.

The RssResult must be capable of taking any data and knowing how to render an RSS item from it (i.e. news, events, posts, comments, reviews etc). So before we create the RssResult we need to abstract the structure of an RSS item. We do this by creating an interface abstraction to accommodate this. Any object that is to be served can implement this interface allowing the RssResult to work with it.

1
2
3
4
5
6
7
8
9
namespace System
{
    public interface IRss
    {
        string Title { get; }
        string Description { get; }
        string Link { get; }
    }
}

The standard return type of a controller’s methods is the abstract type ActionResult. In most cases the actual returned object will be of type ViewResult, which will look for a view to use when rendering your model to the response stream. Here, our RssResult will inherit from ActionResult, just as ViewResult does, but will internally render its own XML output and alter the response’s content-type header to xml, avoiding any view files or extra work in the controller. Using dependency injection the RssResult will not be instantiable without being supplied with the data it has to serve, which will be a List. It will also accept a title and description for the feed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using System;
using System.Collections.Generic;
using System.Xml;
 
namespace System.Web.Mvc
{
    public class RssResult : ActionResult
    {
 
        private List<IRss> _items;
        private string _title;
        private string _description;
 
        /// <summary>
        /// Initialises the RssResult
        /// </summary>
        /// <param name="items">The items to be added to the rss feed.</param>
        /// <param name="title">The title of the rss feed.</param>
        /// <param name="description">A short description about the rss feed.</param>
        public RssResult(IEnumerable<IRss> items, string title, string description)
        {
            _items = new List<IRss>(items);
            _title = title;
            _description = description;
        }
 
        public override void ExecuteResult(ControllerContext context)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.NewLineHandling = NewLineHandling.Entitize;
 
            context.HttpContext.Response.ContentType = "text/xml";
            using (XmlWriter _writer = XmlWriter.Create(context.HttpContext.Response.OutputStream, settings))
            {
 
                // Begin structure
                _writer.WriteStartElement("rss");
                _writer.WriteAttributeString("version", "2.0");
                _writer.WriteStartElement("channel");
 
                _writer.WriteElementString("title", _title);
                _writer.WriteElementString("description", _description);
                _writer.WriteElementString("link", context.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority));
 
                // Individual items
                _items.ForEach(x =>
                {
                    _writer.WriteStartElement("item");
                    _writer.WriteElementString("title", x.Title);
                    _writer.WriteElementString("description", x.Description);
                    _writer.WriteElementString("link", context.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority) + x.Link);
                    _writer.WriteEndElement();
                });
 
                // End structure
                _writer.WriteEndElement();
                _writer.WriteEndElement();
            }            
        }
 
    }
}

As you can see above, the RssResult itself is pretty simple. It takes the collection of IRss items in it’s constructor. By inheriting the abstract type ActionResult it must override ExecuteResult which is called by the framework when it is returned from the controller, which you can see an example of below. One thing to notice in the following code snippet is that the original list of items must be cast to a list of IRss. This is due to the limitations of covariance and contravariance on generics in the C# language. Future versions may support this so that casting will not be required.

1
2
3
4
5
public ActionResult Feed()
        {
            IEnumerable<IRss> news = new NewsService().GetByLatest().Cast<IRss>();
            return new RssResult(news, "William Duffy - Glasgow Based ASP.NET Web Developer", "The latest news on ASP.NET, C# and ASP.NET MVC ");
        }

What is NewsService?

A few people have asked what NewsService is, how does it work and if they could have some sample code. Whenever I model software I separate entities, business logic and data access into their own architectural layers. Basically, NewService.GetByLatest() is in the business logic layer, which calls the data access layer and asks for all the latest news items. I’ve put together a quick and dirty example below for you to get started with. Notice that the data access layer is internal. Final versions would refactor the ADO.NET code, entity mappings etc into reusable blocks.

 
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
 
public class News : IRss
{
    public Guid ID { get; set; }
    public string Title { get; set; }
    public string Preview { get; set; }
    public string Body { get; set; }
    public DateTime DateCreated { get; set; }
 
    #region IRss Members
 
    public string IRss.Title { get { return Title  ;} }
    public string IRss.Description { get { return Preview ;} }
    public string IRss.Link { get { return string.Format("http://www.wduffy.co.uk/news/view.aspx?id={0}", ID); } }
 
    #endregion
}
 
public class NewsService
{
    public List<News> GetByLatest()
    {
        // Do business logic stuff here if required
        return new NewsData().GetByLatest();
    }
}
 
internal class NewsData
{
    public List<News> GetByLatest()
    {
        var result = new List<News>();
 
        using (var conn = new SqlConnection("Your Connection String"))
        {
            var cmd = new SqlCommand("Select TOP 5 ID, Title, Preview, Body, DateCreated FROM tblNews ORDER BY DateCreated DESC", conn);
            conn.Open();
 
            using (var reader = cmd.ExecuteReader())
                while (reader.Read())
                {
                    var news = new News();
                    news.ID = reader.GetGuid(0);
                    news.Title = reader.GetString(1);
                    news.Preview = reader.GetString(2);
                    news.Body = reader.GetString(3);
                    news.DateCreated = reader.GetDateTime(4);
 
                    result.Add(news);
                }
        }
 
        return result;
    }
}