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).

DynamicMethod vs Reflection

Introduction

DynamicMethod was introduced in .Net 2.0, and in this article I am going to explain how to use DynamicMethod class as an alternative to Reflection to achieve a better performance.

DynamicMethod is the most efficient way to generate and execute a small amount of code. For instance, you can leverage DynamicMethod class to replace System.Reflection by generating and executing methods at run time, without having to generate dynamic assemblies and dynamic types to contain the methods. The executable code created by the JIT compiler will be reclaimed when the DynamicMethod object is reclaimed. This approach is much faster and provides exceptional performance comparing to using Reflection.

The Problem

System.Reflection is too slow and will cost you performance degradation if you decide to use it heavily in your application. However, in some domains like building compilers or ORM databases, using reflection is inevitable.

The Solution

Luckily, DynamicMethod could substitute Reflection, while offering a significant performance increase. To prove that, I have created an application which instantiates new objects in a tight loop using Reflection, DynamicMethod and regular object instantiation using new keyword.

InstantiateObject

The above screenshot shows the results of running the code in a tight loop 1,000,000,00 times. As shown, the DynamicMethod is roughly 6 times faster than Reflection (You may get different results when you try it on your machine), while is almost close to using the new keyword.

public delegate object InstanceCreator();
public InstanceCreator CreateInstance() where T : class
{
    InstanceCreator result = null;
    var constructor = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
    if (constructor != null)
    {
        var dynamicMethod = new DynamicMethod("InstantiateObject", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), null, typeof(T), true);
        var codeGenerator = dynamicMethod?.GetILGenerator();
        if (codeGenerator != null)
        {
            codeGenerator.Emit(OpCodes.Newobj, constructor);
            codeGenerator.Emit(OpCodes.Ret);
            result = dynamicMethod.CreateDelegate(typeof(InstanceCreator)) as InstanceCreator;
        }
    }

    return result;
}

The InstanceCreator delegate is as a return type of the CreateInstance method which creates and compile code dynamically. The delegate then points to this generated code which can be invoked later to perform the specified task.

The same idea could be applied to create setters and getters delegates to set/get private fields using DynamicMethod class as shown blow

public delegate object Getter(object source);
public Getter CreateGetter(FieldInfo fieldInfo) where T : class
{
    Getter result = null;
    var dynamicGet = new DynamicMethod("DynamicGet", typeof(object), new Type[] { typeof(object) }, typeof(T), true);
    var getGenerator = dynamicGet?.GetILGenerator();
    if (getGenerator != null)
    {
        getGenerator.Emit(OpCodes.Ldarg_0);
        getGenerator.Emit(OpCodes.Ldfld, fieldInfo);
        BoxIfNeeded(fieldInfo.FieldType, getGenerator);
        getGenerator.Emit(OpCodes.Ret);
        result = dynamicGet.CreateDelegate(typeof(Getter)) as Getter;
    }

    return result;
}

public delegate void Setter(object source, object value);
public Setter CreateSetter(FieldInfo fieldInfo) where T : class
{
    Setter result = null;

    var dynamicSet = new DynamicMethod("DynamicSet", typeof(void), new Type[] { typeof(object), typeof(object) }, typeof(T), true);
    var setGenerator = dynamicSet?.GetILGenerator();

    if (setGenerator != null)
    {
        setGenerator.Emit(OpCodes.Ldarg_0);
        setGenerator.Emit(OpCodes.Ldarg_1);
        UnboxIfNeeded(fieldInfo.FieldType, setGenerator);
        setGenerator.Emit(OpCodes.Stfld, fieldInfo);
        setGenerator.Emit(OpCodes.Ret);

        result = dynamicSet.CreateDelegate(typeof(Setter)) as Setter;
    }
    return result;
}

private void BoxIfNeeded(Type type, ILGenerator generator)
{
    if (type.IsValueType)
        generator.Emit(OpCodes.Box, type);
}

private void UnboxIfNeeded(Type type, ILGenerator generator)
{
    if (type.IsValueType)
        generator.Emit(OpCodes.Unbox_Any, type);
}

You could encapsulate all these methods into a DynamicMethodFactory class and use them as the following

DynamicMethodFactory dynamicMethodFactory = new DynamicMethodFactory();
InstanceCreator instanceCreator = dynamicMethodFactory.CreateInstance();
object result = instanceCreator();
DynamicMethodFactory dynamicMethodFactory = new DynamicMethodFactory();
FieldInfo fieldInfo = typeof(TestClass).GetField("property", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Setter setter = dynamicMethodFactory.CreateSetter(fieldInfo);
setter(testClass, "test");
DynamicMethodFactory dynamicMethodFactory = new DynamicMethodFactory();
FieldInfo fieldInfo = typeof(TestClass).GetField("property", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Getter getter = dynamicMethodFactory.CreateGetter(fieldInfo);
string value = (string)getter(testClass);

 

Conclusion

DynamicMethod class offers a significant improve to system performance by creating and compiling code at run time. This approach is much faster than using System.Reflection as proven by the results above.

Thank you for reading my article. I wish it helps.

How to use Unity Interception to create Attribute Based Cache

1- Download Unity Interception using NuGet.

2- Create a custom attribute

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CacheAttribute : Attribute
{
    public double AbsoluteExpiration { get; private set; }

    public CacheAttribute(double absoluteExpiration)
    {
        AbsoluteExpiration = absoluteExpiration;
    }
}

3- Create  Interception Behavior

public class CachingInterceptionBehavior : IInterceptionBehavior
{
    private object _LockSync = new object();

    public bool WillExecute
    {
        get
        {
            return true;
        }
    }

    public CachingInterceptionBehavior() { }

    public IEnumerable GetRequiredInterfaces()
    {
        return Type.EmptyTypes;
    }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        IMethodReturn result = null;

        CacheAttribute cacheAttr = input.Target.GetType().GetMethods().FirstOrDefault(m => m.Name == input.MethodBase.Name)
            ?.GetCustomAttributes(typeof(CacheAttribute), false).FirstOrDefault() as CacheAttribute;

        if (cacheAttr != null)
        {
            result = GetItem(input.MethodBase.Name, false) as IMethodReturn;
            if (result == null)
            {
                result = InvokeSource(input, getNext);
                if (result.Exception == null)
                    AddItem(input.MethodBase.Name, result, cacheAttr.AbsoluteExpiration);
            }
        }

        if (result == null)
            result = InvokeSource(input, getNext);

        return result;
    }

    private IMethodReturn InvokeSource(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        return getNext()(input, getNext);
    }

    private void AddItem(string key, object value, double seconds)
    {
        lock (_LockSync)
        {
            MemoryCache.Default.Add(key, value, DateTimeOffset.Now.AddSeconds(seconds));
        }
    }

    private void RemoveItem(string key)
    {
        lock (_LockSync)
        {
            MemoryCache.Default.Remove(key);
        }
    }

    private object GetItem(string key, bool remove)
    {
        lock (_LockSync)
        {
            var res = MemoryCache.Default[key];

            if (res != null)
            {
                if (remove == true)
                    MemoryCache.Default.Remove(key);
            }

            return res;
        }
    }
}

4- Register both the Interception extension and the new Cache behavior

Container.AddNewExtension()
         .RegisterType(new ContainerControlledLifetimeManager());

5- That’s it. It’s time to start decorating your functions

[Cache(absoluteExpiration: 60)]
public async Task GetValue()
{
  .....
}