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 reader
using 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
public class TheSiteDoctor
protected SharedBasePage requestPage;
protected Entry currentEntry;
public TheSiteDoctor(SharedBasePage page, Entry entry)
requestPage = page;
currentEntry = entry;
}
/// <summary>
/// A dasBlog macro to retrieve an RSS feed and apply XSL to
/// it before caching it for x minutes
/// </summary>
/// <param name="xslVPath">The virtual path of the XSL file</param>
/// <param name="rssPath">The RSS feed URL</param>
/// <param name="minutesToCache">Number of minutes to cache the file for</param>
/// <param name="debugMode">Output the debug information</param>
/// <returns>A control that can be inserted into a dasBlog template</returns>
public virtual 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;
if (debugMode)
output.Append("<strong><start debug></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");
}
if (minutesToCache > 0)
writeToCache = true;
//Find the cache directory
string cacheDir = HttpContext.Current.Server.MapPath(cacheVDir);
//Work out what the file would be called based on the RSS URL
cachedFileLoc = Path.Combine(cacheDir, HttpUtility.UrlEncode(TheSiteDoctor.GetMd5Sum(rssPath)) + ".cache");
if (debugMode)
output.AppendFormat("<i>cache file</i>: {0}\r\n", cachedFileLoc);
}
cacheExpired = true;
if (debugMode)
output.Append("<i>cache age</i>: no file exists<br />");
}
FileInfo info1 = new FileInfo(cachedFileLoc);
TimeSpan span1 = (TimeSpan)(DateTime.Now - info1.LastWriteTime);
if (span1.TotalMinutes > minutesToCache)
cacheExists = true;
cacheExpired = true;
}
if (debugMode)
output.AppendFormat("<i>cache age</i>: : {0} min old <br />\r\n", span1.TotalMinutes);
}
if (debugMode)
output.Append("<strong>caching disabled - CacheStorageAgeLimit=0</strong><br /><span style=\"color:red; font-weight: bold;\">FYI: All requests to this page will cause a new server request to the RssPath</span><br />");
}
}
#endregion
if (debugMode)
output.Append("<hr />");
}
if (cacheExpired)
if (cacheExists & debugMode)
output.Append("<strong>file cache is expired, getting a new copy right now</strong><br />");
}
output.Append("<strong>no cache, getting file</strong><br />");
}
output.Append(TheSiteDoctor.delegateRss(xslVPath, rssPath, 0, writeToCache, cachedFileLoc, debugMode));
}
if (debugMode)
output.Append("<strong>cool, we got the file from cache</strong><br />");
}
StreamReader reader1 = File.OpenText(cachedFileLoc);
output.Append(reader1.ReadToEnd());
reader1.Close();
}
if (debugMode)
output.Append("<hr /><strong><end debug></strong>");
}
output.Append("\r\n<!-- \r\ndasBlog RSS feed produced using the macro from Tim Gaunt\r\nhttp://blogs.thesitedoctor.co.uk/tim/\r\n-->");
return new LiteralControl(output.ToString());
}
/// <summary>
/// RSS feed retrieval worker method. Retrieves the RSS feed
/// and applies the specified XSL document to it before caching
/// a copy to the disk -this should be called after it has been
/// established the cache is out of date.
/// </summary>
/// <param name="xslVPath">The virtual path of the XSL file</param>
/// <param name="rssPath">The RSS feed URL</param>
/// <param name="timeoutSeconds">Number of seconds before the request should timeout</param>
/// <param name="writeCache">Whether to cache a copy on disk</param>
/// <param name="xmlPath">Physical path of the XML file on the disk</param>
/// <param name="debugMode">Output the debug information</param>
/// <returns>An XML document as a string</returns>
private static string 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: Replace this with a HttpRequest and timeout to ensure the visitor is not left waiting for the file to load
//Load the XML
System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.Load(rssPath);
//Load the XSL
System.Xml.Xsl.XslTransform xslDoc = new System.Xml.Xsl.XslTransform();
xslDoc.Load(xslPath);
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
//Apply the XSL to the XML document
xslDoc.Transform(xmlDoc, null, sw);
//Append the resulting code to the output file
output.Append(sb.ToString());
}
errorThrown = true;
if (debugMode)
//Log the exception to the dasBlog exception handler
ErrorTrace.Trace(TraceLevel.Error, ex);
output.AppendFormat("<ul style=\"\"><li><strong>RSS request failed :(</strong> <br />{0}</li></ul>", ex.ToString());
}
//Save a cache of the returned RSS feed if no errors occured
if (writeCache & !errorThrown)
//Find the cache's storage directory
DirectoryInfo dir = new DirectoryInfo(HttpContext.Current.Server.MapPath(cacheVDir));
//Check it exists
if (!dir.Exists)
dir.Create();
if (debugMode)
output.AppendFormat("<strong>just created the directory:</strong> {0}<br />", HttpContext.Current.Server.MapPath(cacheVDir));
}
StreamWriter writer1 = File.CreateText(xmlPath);
writer1.Write(output);
writer1.Flush();
writer1.Close();
if (debugMode)
output.Append("<strong>just wrote the new cache file</strong><br />");
}
return output.ToString();
}
/// <summary>
/// Worker method to identify the MD5 checksum of a string
/// in this instance used to ensure the RSS file isn't already
/// cached (based on the URL supplied)
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string GetMd5Sum(string str)
Encoder encoder1 = Encoding.Unicode.GetEncoder();
byte[] buffer1 = new byte[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"));
}
}
}
XSL that I use for Last.FM
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.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 - The Social Music Revolution"><img alt="last.fm - The Social Music Revolution" 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)