How to create Apple iTunes and Google Play compatible RSS feed using ASP.NET

In order to create a podcast on Google Play or Apple iTunes, you need to create a valid RSS feed that conforms to the RSS 2.0 specifications and also to include their required tags. Luckily, both companies accept each other’s standards. In other words, if you decide to implement Apple’s tags, you won’t need to change or modify your feed to support Google’s standards.

In this article I’m going to explain how to create a feed that is compatible with both companies. Let’s assume you are building a feed for weekly church sermons, here is what you need to do:

1- Define a RSS element that includes iTunes & Atom namespace:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
</rss>

2- Inside the rss element, create a channel element which contains the following sub-elements:

  • <title> Title of the Podcast’s
  • <link> The feed’s URL
  • <atom:link> Same as <link>
  • <language> The ISO language used in the podcast. See http://www.loc.gov/standards/iso639-2/php/code_list.php for the languages list.
  • <copyright> The organization copyrights
  • <itunes:author> Author of the podcast (up to 255 characters)
  • <itunes:summary> Description of the podcast (up to 4000 characters)
  • <description> Same as summary
  • <itunes:owner> After the feed is submitted to either companies, they will send a verification email to the email provided in the sub element. This element contains two sub elements
    • <itunes:name> The organization’s name
      (up to 255 characters)
    • <itunes:email> Email address to authenticate podcast ownership (up to 255 characters). Make sure you have access to that email
  • <itunes:image> Image shown on Google Play or iTunes to identify your podcast (URL can include up to 2048 characters). The image has to be at least 1400 x 1400 pixels and at maximum 3000 x 3000 pixels in JPEG or PNG format
  • <itunes:category> The podcast’s category. See https://help.apple.com/itc/podcasts_connect/#/itc9267a2f12 for a complete supported category and subcategory list.
  • <itunes:explicit> If your podcast has explicit content, you need to add the appropriate tag on your podcast’s RSS feed. See https://support.google.com/googleplay/podcasts/answer/6260345 to learn more about identifying explicit content.

The following is an example of a valid channel element

<channel>
  <title>Church Sermon</title>
  <link>http://www.church.com/Sermon</link>
  <atom:link>http://www.church.com/Sermon</atom:link>
  <language>en-us</language>
  <copyright>Church Copyright</copyright>
  <itunes:author>Author</itunes:author>
  <itunes:summary>Weekly Sermons</itunes:summary>
  <description>Church Weekly Sermons</description>
  <itunes:owner>
    <itunes:name>Church</itunes:name>
    <itunes:email>church@church.com</itunes:email>
  </itunes:owner>
  <itunes:image href="http://www.church.com/img/church.jpg" />
  <itunes:category text="Religion & Spirituality">
    <itunes:category text="Christianity" />
  </itunes:category>
  <itunes:explicit>no</itunes:explicit>
</channel>

3- Create a list of item elements (one for each episode) inside the channel element. Each item with the following sub-elements:

  • <title> The episode name
  • <itunes:author> The episode speaker (up to 255 characters)
  • <itunes:summary> The episode description (up to 4000 characters)
  • <enclosure> This element contains three attributes:
    • URL: The URL attribute points to your podcast content
    • length: The length attribute is the file size in bytes
    • type: The type attribute provides the correct category for the type of file you are using. The type values for the supported file formats are: audio/x-m4a, audio/mpeg, video/quicktime, video/mp4, video/x-m4v, and application/pdf.
  • <guid> Permanent, case-sensitive Globally Unique Identifier for a podcast episode. Each episode needs a GUID. GUIDs are compared to indicate which episodes are new. If the guid is not a link, set the isPermaLink attribute to false (As shown in the example)
  • <pubDate> The episode publication date shown to users
  • <itunes:explicit> Described above.

The following is an example of a valid item

<item>
  <title>episode 1</title>
  <itunes:author>Fr. James</itunes:author>
  <itunes:summary>This is the first episode</itunes:summary>
  <enclosure url="http://church.com/sermons/episode1.mp4" type="audio/x-mp3" length="6957974" />
  <guid isPermaLink="false">episode1.mp4</guid>
  <pubDate>Sat, 19 Jan 2019 00:00:00 +00:00</pubDate>
  <itunes:explicit>no</itunes:explicit>
</item>

If we put together what we have discussed we’ll end up with the following implementation

[HttpGet]
[HttpHead]
[AllowAnonymous]
public void RssFeed()
{
    XNamespace itunesNs = "http://www.itunes.com/dtds/podcast-1.0.dtd";
    XNamespace atomNs = "http://www.w3.org/2005/Atom";
    var feed = new XDocument(
        new XDeclaration("1.0", "utf-8", "yes"),
        new XElement("rss",
        new XAttribute("version", "2.0"),
        new XAttribute(XNamespace.Xmlns + "itunes", itunesNs.NamespaceName),
        new XAttribute(XNamespace.Xmlns + "content", "http://purl.org/rss/1.0/modules/content/"),
        new XAttribute(XNamespace.Xmlns + "atom", atomNs.NamespaceName),
            new XElement("channel",
                new XElement("title", "Church Sermon"),
                new XElement("link", new Uri(Url.Action(nameof(Index), "Sermon", null, Request.Scheme))),
                new XElement(atomNs + "link", new Uri(Url.Action(nameof(Index), "Sermon", null, Request.Scheme))),
                new XElement("language", "en-us"),
                new XElement("copyright", "Church Copyright"),
                new XElement(itunesNs + "author", "Author"),
                new XElement(itunesNs + "summary", "Weekly Sermons"),
                new XElement("description", "Church Weekly Sermons"),
                new XElement(itunesNs + "owner",
                    new XElement(itunesNs + "name", "Church"),
                    new XElement(itunesNs + "email", "church@church.com")),
                new XElement(itunesNs + "image", new XAttribute("href", new Uri(Request.Scheme + "://" + Request.Host.Value + "/img/church.jpg"))),
                new XElement(itunesNs + "category", new XAttribute("text", "Religion & Spirituality"),
                    new XElement(itunesNs + "category", new XAttribute("text", "Christianity"))),
                new XElement(itunesNs + "explicit", "no"),
                _Context.Sermons.Include(s => s.Author).AsNoTracking().ToList()
                .Select(s =>
                {
                    return new XElement("item",
                        new XElement("title", s.Title),
                        new XElement(itunesNs + "author", "Fr. " + s.Author.FirstName + " " + s.Author.LastName),
                        new XElement(itunesNs + "summary", s.Title),
                        new XElement("enclosure", new XAttribute("url", s.AudioUrl), new XAttribute("type", "audio/x-mp3"), new XAttribute("length", s.Size)),
                        new XElement("guid", s.AudioBlobId, new XAttribute("isPermaLink", "false")),
                        new XElement("pubDate", s.Date.ToString("ddd, dd MMM yyyy HH:mm:ss zzzz")),
                        new XElement(itunesNs + "explicit", "no"));
                }))));

    Response.ContentType = "application/rss+xml";
    using (XmlWriter writer = XmlWriter.Create(Response.Body))
    {
        feed.WriteTo(writer);
        writer.Close();
    }

    Response.Body.Flush();
}

The above controller action method implementation supports Head requests, which is a way of allowing Google Play or iTunes to get information about the episodes without downloading them.

Also, keep in your mind that this method will be called frequently according to your podcast popularity. Consequently, I’d recommend for an actual implementation to cache the request, or at least the database calls (querying the sermons in this case).

Leave a comment