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><startdebug></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><enddebug></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)