One other way to think of a “content management system” is as a database engineered to present data in a rich and navigable format. As such, a CMS doesn’t have to be “the company blog;” and a business’ communications – especially the kind that used to be logged on the intranet – don’t have to look like WordPress.
A few months later than originally planned, the “RTM” code for version 5 of the open source Umbraco CMS has made its way to CodePlex, Microsoft’s open source distribution channel. Now the hard part begins: updating, and in many cases replacing, existing documentation that goes back to version 3.
The various elements of Umbraco’s CMS database are referred to as document types. You define how a document type functions and operates by way of macros (not how it looks; that’s done with CSS). From version 3 of Umbraco, which was only a few years ago, up until now, there have been unofficially three, and actually four somewhat different methodologies for producing macros. Version 3 relied on XSLT, a W3C standard since the late 1990s. XSLT effectively leverages XML to embed server-side instructions into an HTML page, that translates data from an outside source into content and HTML markup. The client never sees the code, or, to use W3C’s term for it, the stylesheet, even though it wasn’t being used for style in this context.
XSLT seemed to be the right choice at first, because it was inarguably a world standard, nobody owned it, and it was well documented. It was terribly cumbersome, however. Here’s an excerpt from a CMS I created for Umbraco v3 two years ago. It’s a code segment that projects the three newest headlines from a blog with my name.
<xsl:param name=”currentPage”/>
<xsl:template match=”/”>
<ul>
<xsl:for-each select=”$currentPage/ancestor-or-self::root/descendant-or-self::node [@nodeName=’Scott Fulton’]/node [string(data [@alias=’umbracoNaviHide’]) != ‘1’]”>
<xsl:sort select=”data [@alias = ‘DisplayDate’]” order=”descending” />
<xsl:if test=”position() <= 3″>
<li>
<a href=”{umbraco.library:NiceUrl(@id)}”>
<xsl:value-of select=”data [@alias=’DisplayHeadline’]”/>
</a>
</li>
</xsl:if>
</xsl:for-each>
</ul>
It looks far more confusing than it actually is. The easiest part is spotting the pure HTML, such as the UL block where the results are rendered. You can spot certain global variables, or parameters, by the $
prefix, as in $currentPage
. Then the whole bit about ancestor-or-self::root
and all that is part of another W3C standard called XPath, which is used here to navigate through the database tree. To make sure the articles are sorted by the date of publication as opposed to the date of creation, I invoked the DisplayDate
variable as the sort parameter sent to the xsl:sort
instruction. (Every single XSLT instruction has an xsl:
prefix. I sometimes spent hours wondering why some macros were stuck, only to find I had written “xslt:” as the prefix.)
But the layers of abstraction between the variable name and the XSLT instruction were three-fold, and this was the case all the time. select="data [@alias = 'DisplayDate']"
used single quotes to denote the variable separate from the attribution, which was always preceded by @alias
(JavaScript devs, imagine always having to write “the name of the variable is” before every variable), which is demarcated by [square brackets] that separate the attribution from the data
market (“the following data is data”), which is then set off from the property name using double-quotes. The result looked like the cat had danced on the keyboard, and few XSLT veterans would claim it as legitimate.
With version 4, the Umbraco developers began taking controversial, but necessary, steps to move toward a more sensible approach. As Microsoft began its major push for an architecture called MVC (Model / View / Controller) for ASP.NET, it only made sense that Umbraco adopt it somehow. It fit perfectly with Umbraco’s original philosophy of separating the data from the rendering of the data, and separating that from the device that controls it.
But the easiest route to embracing MVC appeared to be the direct one, which with respect to a Microsoft technology meant embracing a Microsoft language: C#. After Microsoft made available a broader array of development tools such as WebMatrix that didn’t tie devs down to using the commercial Visual Studio, the skepticism winded down.
Now using Umbraco’s third iteration of Microsoft’s Razor – its scripting language based on C# – it is entirely feasible for the above monstrosity of an excerpt to be boiled down to a single instruction.
With thanks to long-time Umbraco contributor Warren Buckley, here’s a complete example of the change in legibility from XSLT to Umbraco v5 Razor. The following two excerpts do the exact same thing. Here’s the Umbraco v3 version:
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp ” “> ]>
<xsl:stylesheet
version=”1.0″
xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”
xmlns:msxml=”urn:schemas-microsoft-com:xslt”
xmlns:umbraco.library=”urn:umbraco.library” {0}
exclude-result-prefixes=”msxml umbraco.library {1}”>
<xsl:output method=”xml” omit-xml-declaration=”yes” />
<xsl:param name=”currentPage”/>
<!– Don’t change this, but add a ‘contentPicker’ element to –>
<!– your macro with an alias named ‘source’ –>
<xsl:variable name=”source” select=”/macro/source”/>
<xsl:template match=”/”>
<!– The fun starts here –>
<ul>
<xsl:for-each select=”umbraco.library:GetXmlNodeById($source)/node [string(data [@alias=’umbracoNaviHide’]) != ‘1’]”>
<li>
<a href=”{umbraco.library:NiceUrl(@id)}”>
<xsl:value-of select=”@nodeName”/>
</a>
</li>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>
Here is the Umbraco v5 version:
@inherits PartialViewMacroPage
@using Umbraco.Cms.Web
@using Umbraco.Cms.Web.Macros
@using Umbraco.Framework
@{
//Get the macro parameter and check it has a value otherwise set to empty hive Id
var startNodeID = String.IsNullOrEmpty(Model.MacroParameters.startNode) ? HiveId.Empty.ToString() : Model.MacroParameters.startNode;
//Check that we have a HiveId to work with
if (startNodeID != HiveId.Empty.ToString())
{
var startNode = Umbraco.GetDynamicContentById(startNodeID);
//Check that startNode has child pages
if (startNode.Children.Any())
{
<ul>
@foreach (var page in startNode.Children)
{
<li><a href=”@page.Url”>@page.Name</a></li>
}
</ul>
}
}
else
{
<p>HiveId is empty</p>
}
}