DasBlog RSS Feed Macro

As part of my blog’s re-design I wanted to integrate my statistics from Last.FM which monitors what music you’re listening to and generates a stack of statistics about your listening habit (see About Last FM for more information).

Anyways, I started writing my own RSS macro when I came across one already developed by John Forsythe (http://www.jforsythe.com/) which did pretty much exactly what I was planning on developing, the only difference though was that his was hard-coded to preset node names whereas I was planning on using an XSL file to format mine to offer maximum flexibility in the long run so I updated his with the use of reflector (thanks to John Forsythe though!!).

There are a couple of difference to note with this code and John Forsythe's:

  • The RSS retrieval is no longer handled by an external library -in this instance I wanted to keep this as simple and stand-alone as possible. There is no max item count at present -this is mainly because I didn't need it for the Last.FM Feed, I may alter that later.

Source code for a dasBlog XSL based RSS readerusing System;using System.IO;using System.Security.Cryptography;using System.Diagnostics;using System.Text;using System.Web;using System.Web.UI;using newtelligence.DasBlog.Runtime;using newtelligence.DasBlog.Web.Core;namespace TSDMacros{...}{publicclass TheSiteDoctor{...}    {protected SharedBasePage requestPage;protected Entry currentEntry;public TheSiteDoctor(SharedBasePage page, Entry entry){...}        {            requestPage = page;            currentEntry = entry;        }///<summary>///AdasBlogmacrotoretrieveanRSSfeedandapplyXSLto///itbeforecachingitforxminutes///</summary>///<param name="xslVPath">ThevirtualpathoftheXSLfile</param>///<param name="rssPath">TheRSSfeedURL</param>///<param name="minutesToCache">Numberofminutestocachethefilefor</param>///<param name="debugMode">Outputthedebuginformation</param>///<returns>AcontrolthatcanbeinsertedintoadasBlogtemplate</returns>publicvirtual Control GetRSS(string xslVPath, string rssPath, int minutesToCache, bool debugMode){...}        {string cacheVDir = "./content/getrsscache/";string cachedFileLoc = String.Empty;            StringBuilder output = new StringBuilder();bool writeToCache = false;bool cacheExpired = false;bool cacheExists = false;Debug output#region Debug outputif (debugMode){...}            {                output.Append("<strong>&lt;startdebug&gt;</strong><hr/>\r\n");                output.AppendFormat("<i>RssPath</i>:{0}<br/>\r\n", rssPath);                output.AppendFormat("<i>minutesToCache</i>:{0}<br/>\r\n", minutesToCache);                output.AppendFormat("<i>CacheStorageFolder</i>:{0}<br/>\r\n", cacheVDir);                output.Append("<hr/>\r\n");            }#endregionCheck whether we need to cache or not#region Check whether we need to cache or notif (minutesToCache > 0){...}            {                writeToCache = true;//Findthecachedirectorystring cacheDir = HttpContext.Current.Server.MapPath(cacheVDir);//WorkoutwhatthefilewouldbecalledbasedontheRSSURL                cachedFileLoc = Path.Combine(cacheDir, HttpUtility.UrlEncode(TheSiteDoctor.GetMd5Sum(rssPath)) + ".cache");Debug output#region Debug outputif (debugMode){...}                {                    output.AppendFormat("<i>cachefile</i>:{0}\r\n", cachedFileLoc);                }#endregionif (!File.Exists(cachedFileLoc)){...}                {                    cacheExpired = true;Debug output#region Debug outputif (debugMode){...}                    {                        output.Append("<i>cacheage</i>:nofileexists<br/>");                    }#endregion                }else{...}                {                    FileInfo info1 = new FileInfo(cachedFileLoc);                    TimeSpan span1 = (TimeSpan)(DateTime.Now - info1.LastWriteTime);if (span1.TotalMinutes > minutesToCache){...}                    {                        cacheExists = true;                        cacheExpired = true;                    }Debug output#region Debug outputif (debugMode){...}                    {                        output.AppendFormat("<i>cacheage</i>::{0}minold<br/>\r\n", span1.TotalMinutes);                    }#endregion                }            }else{...}            {Debug output#region Debug outputif (debugMode){...}                {                    output.Append("<strong>cachingdisabled-CacheStorageAgeLimit=0</strong><br/><spanstyle=\"color:red;font-weight:bold;\">FYI:AllrequeststothispagewillcauseanewserverrequesttotheRssPath</span><br/>");                }#endregion                cacheExpired = true;            }#endregionDebug output#region Debug outputif (debugMode){...}            {                output.Append("<hr/>");            }#endregion//Checkwhetherornotthecachehasexpiredif (cacheExpired){...}            {Debug output#region Debug outputif (cacheExists & debugMode){...}                {                    output.Append("<strong>filecacheisexpired,gettinganewcopyrightnow</strong><br/>");                }elseif (debugMode){...}                {                    output.Append("<strong>nocache,gettingfile</strong><br/>");                }#endregion//Thecachehasexpiredsoretrieveanewcopy                output.Append(TheSiteDoctor.delegateRss(xslVPath, rssPath, 0, writeToCache, cachedFileLoc, debugMode));            }else{...}            {Debug output#region Debug outputif (debugMode){...}                {                    output.Append("<strong>cool,wegotthefilefromcache</strong><br/>");                }#endregion//Thecachestillexistsandisvalid                StreamReader reader1 = File.OpenText(cachedFileLoc);                output.Append(reader1.ReadToEnd());                reader1.Close();            }Debug output#region Debug outputif (debugMode){...}            {                output.Append("<hr/><strong>&lt;enddebug&gt;</strong>");            }#endregion            output.Append("\r\n<!--\r\ndasBlogRSSfeedproducedusingthemacrofromTimGaunt\r\nhttp://blogs.thesitedoctor.co.uk/tim/\r\n-->");returnnew LiteralControl(output.ToString());        }///<summary>///RSSfeedretrievalworkermethod.RetrievestheRSSfeed///andappliesthespecifiedXSLdocumenttoitbeforecaching///acopytothedisk-thisshouldbecalledafterithasbeen///establishedthecacheisoutofdate.///</summary>///<param name="xslVPath">ThevirtualpathoftheXSLfile</param>///<param name="rssPath">TheRSSfeedURL</param>///<param name="timeoutSeconds">Numberofsecondsbeforetherequestshouldtimeout</param>///<param name="writeCache">Whethertocacheacopyondisk</param>///<param name="xmlPath">PhysicalpathoftheXMLfileonthedisk</param>///<param name="debugMode">Outputthedebuginformation</param>///<returns>AnXMLdocumentasastring</returns>privatestaticstring delegateRss(string xslVPath, string rssPath, int timeoutSeconds, bool writeCache, string xmlPath, bool debugMode){...}        {            StringBuilder output = new StringBuilder();bool errorThrown = false;string cacheVDir = "./content/getrsscache/";string xslPath = HttpContext.Current.Server.MapPath(xslVPath);try{...}            {//TODO:ReplacethiswithaHttpRequestandtimeouttoensurethevisitorisnotleftwaitingforthefiletoload//LoadtheXML                System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();                xmlDoc.Load(rssPath);//LoadtheXSL                System.Xml.Xsl.XslTransform xslDoc = new System.Xml.Xsl.XslTransform();                xslDoc.Load(xslPath);                StringBuilder sb = new StringBuilder();                StringWriter sw = new StringWriter(sb);//ApplytheXSLtotheXMLdocument                xslDoc.Transform(xmlDoc, null, sw);//Appendtheresultingcodetotheoutputfile                output.Append(sb.ToString());            }catch (Exception ex){...}            {                errorThrown = true;Debug output#region Debug outputif (debugMode){...}                {//LogtheexceptiontothedasBlogexceptionhandler                    ErrorTrace.Trace(TraceLevel.Error, ex);                    output.AppendFormat("<ulstyle=\"\"><li><strong>RSSrequestfailed:(</strong><br/>{0}</li></ul>", ex.ToString());                }#endregion            }//SaveacacheofthereturnedRSSfeedifnoerrorsoccuredif (writeCache & !errorThrown){...}            {//Findthecache'sstoragedirectory                DirectoryInfo dir = new DirectoryInfo(HttpContext.Current.Server.MapPath(cacheVDir));//Checkitexistsif (!dir.Exists){...}                {                    dir.Create();Debug output#region Debug outputif (debugMode){...}                    {                        output.AppendFormat("<strong>justcreatedthedirectory:</strong>{0}<br/>", HttpContext.Current.Server.MapPath(cacheVDir));                    }#endregion                }//Createthefile                StreamWriter writer1 = File.CreateText(xmlPath);                writer1.Write(output);                writer1.Flush();                writer1.Close();Debug output#region Debug outputif (debugMode){...}                {                    output.Append("<strong>justwrotethenewcachefile</strong><br/>");                }#endregion            }return output.ToString();        }///<summary>///WorkermethodtoidentifytheMD5checksumofastring///inthisinstanceusedtoensuretheRSSfileisn'talready///cached(basedontheURLsupplied)///</summary>///<param name="str"></param>///<returns></returns>publicstaticstring GetMd5Sum(string str){...}        {            Encoder encoder1 = Encoding.Unicode.GetEncoder();byte[] buffer1 = newbyte[str.Length * 2];            encoder1.GetBytes(str.ToCharArray(), 0, str.Length, buffer1, 0, true);byte[] buffer2 = new MD5CryptoServiceProvider().ComputeHash(buffer1);            StringBuilder builder1 = new StringBuilder();for (int minsToCache = 0; minsToCache < buffer2.Length; minsToCache++){...}            {                builder1.Append(buffer2[minsToCache].ToString("X2"));            }return builder1.ToString();        }    }}

XSL that I use for Last.FM<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.1//EN""http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="html"/><xsl:template match="/"><h2>Recent Tracks</h2><ul><xsl:for-each select="recenttracks/track"><li><a href="{url}"><xsl:value-of select="artist"/> - <em><xsl:value-of disable-output-escaping="yes" select="name"/></em></a></li></xsl:for-each></ul><p><a href="About-Last-FM.aspx" title="last.fm-TheSocialMusicRevolution"><img alt="last.fm-TheSocialMusicRevolution" src="images/lastfm_mini_black.gif"/></a></p></xsl:template></xsl:stylesheet>

To use it on the blog template<% GetRSS("LastFM.xsl", "http://ws.audioscrobbler.com/1.0/user/timgaunt/recenttracks.xml", 25, false)|tsd %>

This is a pretty crude way of doing it IMHO because the XSL transforms the stream directly, eventually I’ll update the code so it includes a timeout (as John’s did) and having seen the performance implications on my blog, make sure the request is made asynchronously.

FWIW I have set my cache value to 25minutes, I did have it as 1min for fun but it killed the blog, why have I set it to 25mins? Well, most of my tracks I would think are 2-3minutes long, as I list 10 tracks at a time that’s 20-30minutes listening time so it’ll still keep a fairly accurate overview of my tracks without having massive performance issues on my blog :)

Incase you don't want to or know how to create this macro as a DLL I have created it for you :)

Download the complete dasBlog RSS feed macro (4KB - MD5 Hash: e3d7d6320109fd07259e8d246b754f13)

Subscribe to TSD

Don’t miss out on the latest posts. Sign up now to get access to the library of members-only posts.
jamie@example.com
Subscribe