Click here to Skip to main content
15,891,976 members
Articles / Web Development / ASP.NET

Generation of web feeds and podcasts

Rate me:
Please Sign up or sign in to vote.
4.09/5 (8 votes)
26 Oct 2008BSD4 min read 31.9K   693   37  
This article describes the generation of web feeds (RSS, Atom, podcasts) using wrapper classes.
using System;
using System.Collections.Generic;
using System.Web;
using System.Xml;

namespace Uncas.Web
{
    /// <summary>
    /// Represents a web feed
    /// </summary>
    public class Feed
    {
        #region Constructor

        // TODO: consider another constructor with the fields required for an iTunes podcast

        /// <summary>
        /// Creates a feed with the provided title and description. Valid RSS.
        /// </summary>
        /// <param name="title">The title of the feed</param>
        /// <param name="description">The description of the feed</param>
        public Feed(string title, string description)
        {
            CheckRequiredValue(title, "title");
            CheckRequiredValue(description, "description");
            // Sets the properties:
            this.Title = title;
            this.Description = description;
            // Initializes the list of items:
            this.Items = new List<FeedItem>();
        }

        /// <summary>
        /// Creates a feed with the provided title, link, description, and author. Valid RSS and Atom.
        /// </summary>
        /// <param name="title">The title of the feed</param>
        /// <param name="description">The description of the feed</param>
        /// <param name="author">The author of the feed</param>
        public Feed(string title, string description, string author)
            : this(title, description)
        {
            CheckRequiredValue(author, "author");
            this.Author = author;
        }

        #endregion

        #region Public properties

        /// <summary>
        /// The title of the feed
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// The description of the feed
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// The author of the feed
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// Alternate url, e.g. of a blog web page with the same content
        /// </summary>
        public string AlternateLink { get; set; }
        /// <summary>
        /// Copyright information
        /// </summary>
        public string Copyright { get; set; }
        /// <summary>
        /// The language of the feed
        /// </summary>
        public string Language { get; set; }
        /// <summary>
        /// Email of the managing editor
        /// </summary>
        public FeedEmailAddress ManagingEditor { get; set; }
        /// <summary>
        /// Email of the webmaster
        /// </summary>
        public FeedEmailAddress WebMaster { get; set; }
        /// <summary>
        /// Categories
        /// </summary>
        public string[] Categories { get; set; }

        /// <summary>
        /// Title of a header image
        /// </summary>
        public string ImageTitle { get; set; }
        /// <summary>
        /// Url to header image
        /// </summary>
        public string ImageUrl { get; set; }

        /// <summary>
        /// The list of items for the feed
        /// </summary>
        public List<FeedItem> Items { get; set; }

        #endregion Public properties

        #region Private properties

        /// <summary>
        /// The total number of enclosures in the feed items of this feed
        /// </summary>
        /// <remarks>
        /// Only when this number is larger than 0, the RSS feed displays iTunes info.
        /// </remarks>
        private int NumberOfEnclosures
        {
            get
            {
                int numberOfEnclosures = 0;
                foreach (FeedItem item in this.Items)
                    if (item.Enclosure != null)
                        numberOfEnclosures++;
                return numberOfEnclosures;
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Writes a RSS feed to the HttpResponse of the HttpContext
        /// </summary>
        /// <param name="context">The HttpContext</param>
        public void WriteRSS(HttpContext context)
        {
            HttpResponse response = context.Response;
            string link = context.Request.Url.AbsoluteUri;

            #region Creates the document

            // Sets the content-type:
            response.Clear();
            response.ContentType = "application/rss+xml";

            // Creates an XmlTextWriter to write the XML data to a string:
            XmlTextWriter writer = new XmlTextWriter(response.OutputStream, System.Text.Encoding.UTF8);
            writer.Formatting = Formatting.Indented;

            // Writes the document:
            writer.WriteStartDocument();

            writer.WriteStartElement("rss");

            if (this.NumberOfEnclosures > 0)
            {
                writer.WriteAttributeString("xmlns:itunes",
                    "http://www.itunes.com/dtds/podcast-1.0.dtd");
                writer.WriteAttributeString("xmlns:media",
                    "http://search.yahoo.com/mrss/");
                writer.WriteAttributeString("xml:lang", this.Language);
            }
            writer.WriteAttributeString("version", "2.0");

            // Creates the channel:
            writer.WriteStartElement("channel");

            #endregion

            #region Feed channel info

            #region Required fields

            writer.WriteElementString("title", this.Title);
            writer.WriteElementString("link", link);
            writer.WriteElementString("description", this.Description);

            #region atom:link
            writer.WriteStartElement("atom:link");
            writer.WriteAttributeString("xmlns:atom", "http://www.w3.org/2005/Atom");
            writer.WriteAttributeString("rel", "self");
            writer.WriteAttributeString("href", link);
            writer.WriteAttributeString("type", "application/rss+xml");
            writer.WriteEndElement();
            #endregion atom:link

            #endregion Required fields

            #region Fields with fixed values (fixed by Ole L. Sørensen)

            writer.WriteElementString("ttl", "60");
            writer.WriteElementString("docs"
                , "http://cyber.law.harvard.edu/rss/rss.html");

            DateTime now = DateTime.UtcNow;
            writer.WriteElementString("pubDate", now.ToString("r"));
            writer.WriteElementString("lastBuildDate", now.ToString("r"));

            #endregion Fields with fixed values

            #region Optional fields

            if (this.Copyright != null)
                writer.WriteElementString("copyright", this.Copyright);
            if (this.Language != null)
                writer.WriteElementString("language", this.Language);
            if (this.ManagingEditor != null)
                writer.WriteElementString("managingEditor", this.ManagingEditor.ToString());
            if (this.WebMaster != null)
                writer.WriteElementString("webMaster", this.WebMaster.ToString());

            // TODO: include rating info?
            // TODO: include cloud info?
            if (this.Categories != null)
            {
                foreach (string category in this.Categories)
                {
                    writer.WriteElementString("category", category);
                }
            }

            #region iTunes info

            if (this.NumberOfEnclosures > 0)
            {
                // TODO: consider itunes:explicit, maybe via an enumeration...
                // Possible values for itunes:explicit: yes, clean, no
                // HACK: I use itunes:explicit="no"
                writer.WriteElementString("itunes:explicit", "no");

                writer.WriteElementString("itunes:subtitle", this.Description);
                writer.WriteElementString("itunes:summary", this.Description);
                if (this.Author != null)
                    writer.WriteElementString("itunes:author", this.Author);

                // TODO: does itunes:owner equal ManagingEditor?
                if (this.ManagingEditor != null)
                {
                    writer.WriteStartElement("itunes:owner");
                    writer.WriteElementString("itunes:name", this.ManagingEditor.RealName);
                    writer.WriteElementString("itunes:email", this.ManagingEditor.EmailAddress);
                    writer.WriteEndElement();
                }

                if (this.ImageUrl != null)
                {
                    writer.WriteStartElement("itunes:image");
                    writer.WriteAttributeString("href", this.ImageUrl);
                    writer.WriteEndElement();
                }

                if (this.Categories != null)
                {
                    foreach (string category in this.Categories)
                    {
                        writer.WriteStartElement("itunes:category");
                        writer.WriteAttributeString("text", category);
                        writer.WriteEndElement();
                    }
                }
            }

            #endregion iTunes info

            #region Image info

            if (this.ImageUrl != null)
            {
                writer.WriteStartElement("image");
                writer.WriteElementString("title", this.ImageTitle);
                writer.WriteElementString("url", this.ImageUrl);
                writer.WriteElementString("link", link);
                writer.WriteEndElement();
            }

            #endregion Image info

            #endregion Optional fields

            #endregion Feed channel info

            #region Feed items

            foreach (FeedItem item in this.Items)
            {
                writer.WriteStartElement("item");

                writer.WriteElementString("title", item.Title);

                #region Optional fields

                if (item.Link != null)
                {
                    writer.WriteElementString("link", item.Link);
                    // TODO: use something else for item guid???
                    writer.WriteElementString("guid", item.Link);
                }
                else
                {
                    // TODO: validation recommendation: "item should contain a guid element"
                }

                if (item.Description != null)
                    writer.WriteElementString("description", item.Description);
                if (item.Author != null)
                    writer.WriteElementString("author", item.Author.ToString());
                if (item.Category != null)
                    writer.WriteElementString("category", item.Category);
                if (item.PublishDate.HasValue)
                    writer.WriteElementString("pubDate", item.PublishDate.Value.ToString("r"));

                #region Enclosure info

                if (item.Enclosure != null)
                {
                    // e.g. pod cast:
                    writer.WriteStartElement("enclosure");
                    writer.WriteAttributeString("url", item.Enclosure.Url);
                    writer.WriteAttributeString("type", item.Enclosure.MimeType);
                    writer.WriteAttributeString("length", item.Enclosure.Length.ToString());
                    writer.WriteEndElement();

                    #region iTunes info
                    if (item.Description != null)
                        writer.WriteElementString("itunes:subtitle", item.Description);
                    if (item.Author != null)
                        writer.WriteElementString("itunes:author", item.Author.EmailAddress);
                    if (item.Description != null)
                        writer.WriteElementString("itunes:summary", item.Description);

                    if (item.ImageUrl != null)
                    {
                        writer.WriteStartElement("itunes:image");
                        writer.WriteAttributeString("href", item.ImageUrl);
                        writer.WriteEndElement();
                    }

                    writer.WriteStartElement("media:content");
                    writer.WriteAttributeString("url", item.Enclosure.Url);
                    writer.WriteAttributeString("fileSize"
                        , item.Enclosure.Length.ToString());
                    writer.WriteAttributeString("type", item.Enclosure.MimeType);
                    writer.WriteEndElement();
                    #endregion iTunes info
                }

                #endregion Enclosure info

                #endregion Optional fields

                writer.WriteEndElement();
            }
            #endregion Feed items

            #region Finishes the document

            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.Close();
            response.End();

            #endregion
        }

        /// <summary>
        /// Writes an Atom feed to the HttpResponse of the HttpContext
        /// </summary>
        /// <param name="context">The HttpContext</param>
        public void WriteAtom(HttpContext context)
        {
            HttpResponse response = context.Response;
            string link = context.Request.Url.AbsoluteUri;

            #region Creates the xml document

            // Set the content-type
            response.Clear();
            response.ContentType = "application/atom+xml";

            // Use an XmlTextWriter to write the XML data to a string...
            XmlTextWriter writer = new XmlTextWriter
                (response.OutputStream, System.Text.Encoding.UTF8);
            writer.Formatting = Formatting.Indented;

            // document info
            writer.WriteStartDocument();
            writer.WriteStartElement("feed");
            writer.WriteAttributeString("xmlns", "http://www.w3.org/2005/Atom");

            #endregion Creates the xml document

            #region Feed info

            #region Required fields

            writer.WriteStartElement("title");
            writer.WriteAttributeString("type", "html");
            writer.WriteString(this.Title);
            writer.WriteEndElement();

            writer.WriteStartElement("link");
            writer.WriteAttributeString("rel", "self");
            writer.WriteAttributeString("type", "application/atom+xml");
            writer.WriteAttributeString("href", link);
            writer.WriteEndElement();

            writer.WriteStartElement("subtitle");
            writer.WriteAttributeString("type", "html");
            writer.WriteString(this.Description);
            writer.WriteEndElement();

            // TODO: use another id than the link?
            writer.WriteElementString("id", link);

            #endregion Required fields

            #region Optional fields

            DateTime now = DateTime.UtcNow;
            writer.WriteElementString("updated"
                , XmlConvert.ToString(now, XmlDateTimeSerializationMode.Utc));

            if (this.Author != null)
            {
                writer.WriteStartElement("author");
                writer.WriteElementString("name", this.Author);
                writer.WriteEndElement();
            }

            if (this.AlternateLink != null)
            {
                writer.WriteStartElement("link");
                writer.WriteAttributeString("rel", "alternate");
                writer.WriteAttributeString("type", "text/html");
                writer.WriteAttributeString("href", this.AlternateLink);
                writer.WriteEndElement();
            }

            #endregion Optional fields

            #endregion Feed info

            #region Writes feed items

            foreach (FeedItem item in this.Items)
            {
                #region item info
                writer.WriteStartElement("entry");

                writer.WriteElementString("title", item.Title);

                #region Optional fields

                if (item.Link != null)
                {
                    writer.WriteStartElement("link");
                    writer.WriteAttributeString("href", item.Link);
                    writer.WriteEndElement();

                    // TODO: should another method for generating a unique item id be used?
                    writer.WriteElementString("id", item.Link);
                }

                // TODO: validation warning: "Two entries with the same value for atom:updated"
                // TODO: do not use datetime.now, but require a publish date?
                DateTime updated = DateTime.UtcNow;
                if (item.PublishDate.HasValue)
                    updated = item.PublishDate.Value;
                writer.WriteElementString("updated"
                    , XmlConvert.ToString(updated
                    , XmlDateTimeSerializationMode.Utc));

                if (item.Description != null)
                {
                    writer.WriteStartElement("content");
                    writer.WriteAttributeString("type", "html");
                    writer.WriteString(item.Description);
                    writer.WriteEndElement();
                }
                else
                {
                    // TODO: consider this (required) element, when there is no description...
                    writer.WriteElementString("content", this.Title);
                }

                if (item.Enclosure != null)
                {
                    writer.WriteStartElement("link");
                    writer.WriteAttributeString("rel", "enclosure");
                    writer.WriteAttributeString("type", item.Enclosure.MimeType);
                    writer.WriteAttributeString("length", item.Enclosure.Length.ToString());
                    writer.WriteAttributeString("href", item.Enclosure.Url);
                    writer.WriteEndElement();
                }

                #endregion Optional fields

                writer.WriteEndElement();
                #endregion
            }
            #endregion

            #region Finishes the document

            writer.WriteEndElement();
            writer.Close();
            response.End();

            #endregion Finishes the document
        }

        #endregion

        #region Public static methods

        /// <summary>
        /// Common method that handles a required string parameters
        /// </summary>
        /// <param name="value">The string parameter</param>
        /// <param name="nameOfValue">The name of the parameter</param>
        public static void CheckRequiredValue(string parameter, string nameOfParameter)
        {
            if (string.IsNullOrEmpty(parameter))
                throw new ArgumentNullException(nameOfParameter,
                    string.Format("It is required that {0} has a value", nameOfParameter));
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer
Denmark Denmark
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions