Blog AboutGalleryPortfolioContact
Kenneth Solberg
Welcome to my blog

XML views in Umbraco

In Umbraco you both can and should cache macros. However at some point the cache will time out and the next visitor will get "hit" with a real macro execution. Even worse, what if the app pool just got recycled and the next visitor gets it all? As your content grows your XSLTs will become slower, especially if you need to do broad XPath queries. How should you deal with this this? Say hello to XML views.

A XML view is a highly specific XML fragment with data extracted from your published content XML that is stored in a separate file. You can achieve this without even one line of code by using Darren Fergusons excellent Feed Cache package. This package lets you specify one or more XML feeds to download and store as a flat files. The URL can point to any internal or external URL returning XML. If the target URL is unavailable the previously downloaded file will be left untouched. The package adds a task in config/UmbracoSettings.config so that feeds will be downloaded periodically.

Example scenario:

Let's say you have a fragment in your published content XML that contains 5000 rich content nodes that needs to be queried with a broad and complex XPath. This is an expensive operation and you'll see the CPU hit the roof every time the XPath is executed.

latestArticles.xslt:

<xsl:for-each select="$someContext//article [@isDoc and string(umbracoNaviHide) != '1']">
<xsl:sort select="@createDate" order="descending" />
<xsl:if test="position() &lt;= 10">
<h1><a href="{umbraco.library:NiceUrl(@id)}"><xsl:value-of select="Header" /></a></h1>
:
</xsl:if>
</xsl:for-each>

Now let's recreate this with a XML view!

First off create a XSLT and call it xmlViews.xslt and copy the logic from latestArticles.xslt

xmlViews.xslt:

<xmlViews>
<latestArticles>
<xsl:for-each select="$someContext//Article [string(umbracoNaviHide) != '1']">
<xsl:sort select="@createDate" order="descending" />
<xsl:if test="position() &lt;= 10">
<article url="{umbraco.library:NiceUrl(@id)}"><xsl:value-of select="Header" /><article>
:
</xsl:if>
</xsl:for-each>
</latestArticles>
:
</xmlViews>

Ps. Add any additional XML views needed inside the xmlViews node.

Next create a template and name it xmlViews and place the xmlViews macro in it.

Next install the Feed Cache package ( download here). When the package is installed open \config\feedcache2.config and add a new feed:

 :
<Feed>
<Url>http://yourdomain/xmlViews</Url>
<LocalFile>xmlViews.xml</LocalFile>
</Feed>
:

Now hit this URL to make it run immediately instead of waiting for the Umbraco scheduled task to execute. This will generate a physical file named xmlViews.xml found under \App_Data\FeedCache:

http://yourdomain/umbraco/plugins/FergusonMoriyama/FeedCache2/FmFeedCache.aspx

Now update your latestArticles.xslt:

<xsl:variable
name="xmlViews"
select="document('../App_Data/FeedCache/xmlViews.xml')/xmlViews" />
<xsl:for-each select="$xmlViews/latestArticles/Article">
<h1><a href="{@url}"><xsl:value-of select="." /></a></h1>
:
</xsl:for-each>

What you got here is in fact a file based caching mechanism that won't "hit" your visitors and that will live even if the app pool is recycled.

In a load balanced farm setup you could let the editor instance do the XML generation and with a SAN/NAS file system have the one or more front instances pick up the generated XML file(s).

I'm using this simple technique a lot and it's really fun to optimize existing sites with it as well and see cold page loads drop from ~1sec to ~0.1sec in matter of minutes of optimization. Recently I've also commented out the feed cache task in umbracoSettings.config and instead used Pingdom to ping that URL to keep the site nice and warm at any time.

8.12.2010