Open source, Sitecore, XSL

XslExtensions be gone – part 3

Finally I’ve gotten the XslCodebehind module added to the Sitecore shared source modules.
Go to http://trac.sitecore.net/XslCodebehind if you want to have a look at the source.

Please give me a shout if you have any comments or if you want to contribute with ideas or coding.

Standard
Sitecore, Uncategorized, XSL

XslExtensions be gone – part 2

As described in part one of my post, we at Pentia have for some time had a bad feeling about the XSLT extensions concept introduced by Microsoft in the XsltTransform class and implemented further in Sitecore. We feel that XSLT’s belong in the UI tier (in a traditional n-tier architecture) and XSLT extensions has a tendency to introduce a mix of UI and business layer functionality and limited by the type restrictions in XSLT.

All developers (in their right minds) knows that asp.net codebehind files should contain UI layer functionality, not business logic, and that one c# codebehind file is bound to one asp.net file. What we want to achieve, is to have the same link between codebehind files in ASP.NET and XSLTs, therefore in our internal projects at Pentia, we have introduced the concept of XSLT codehind files.

So how does that work, you might ask?

Basically, we’ve developed a module which is included in all our projects (through our build environment), which automatically links XSLT extension classes to XSLT files through a common namespace. The module consists of an overridden Sitecore.Web.UI.WebControls.XslFile class, which overrides the AddExtensionObjects function:

  public class XslCodebehindFile : Sitecore.Web.UI.WebControls.XslFile
  {
    protected override void AddExtensionObjects(XsltArgumentList list, Item item)
    {
      //...
    }
  }

The AddExtensionObjects function dynamically determines which .NET class is the codebehind file for the current XSLT and adds it to the XsltArgumentList with a specific namespace (in our case http://www.pentia.net/codebehind). The class to add is determined via a custom .NET Attribute class which decorates the codebehind class (see the example below).

Furthermore the module implements an overridden Sitecore.Web.UI.XslControlRenderingType class:

  public class XslCodebehindControlRenderingType : XslControlRenderingType
  {
    public override Control GetControl(NameValueCollection parameters, bool assert)
    {
      // Returns the XslCodebehindFile class instead of the XslFile class
      // ...
    }
  }

This class is used by the Sitecore renderings engine to return the .NET class which implements a Sitecore rendering type. The class is hooked into Sitecore through the web.config (done automatically though our build environment):

<configuration>
  <!-- ... -->
  <sitecore>
    <!-- ... -->
    <renderingControls>
      <!-- ... -->
      <control template="xsl rendering"
        type="PT.XslCodebehind.XslCodebehindControlRenderingType, PT.XslCodebehind"
        propertyMap="Path=path" />
        <!-- ... -->

So how does the developer actually use it?

The developer starts by writing his XSLT file, e.g. /xsl/newslist.xslt. Most often it turns out that XSLT files does not even require codebehind or that the required functionality is implemented in the Sitecore XSLT extensions, but in the rare cases where advanced functionality is required (e.g. paging in the list of news) the developer creates a c# class file named newslist.xslt.cs (to keep the naming conventions defined by Microsoft). The class in the file is decorated with the XslCodebehind attribute which points to the fully qualified XSLT file:

  [XslCodebehind("/xsl/NewsArchive.xslt")]
  public class NewsArchive
  {
    //All functions in this class can be called in NewsArchive.xslt
    public Int32 GetCurrentPageIndex(XPathNodeIterator archive)
    {
      //...
    }
    public Int32 GetPreviousPageIndex(XPathNodeIterator archive)
    {
      //...
    }
    public Int32 GetNextPageIndex(XPathNodeIterator archive)
    {
      //...
    }
    }

This class is then automatically linked into the XSLT file at runtime and can be called though the http://www.pentia.net/codebehind namespace:

  <?xml version="1.0" encoding="UTF-8"?>
  <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:sc="http://www.sitecore.net/sc"
    xmlns:codebehind="http://www.pentia.net/codebehind"
    exclude-result-prefixes="sc codebehind">
    <!-- ... -->
    <xsl:template match="*" mode="main">
      <!-- ... -->
      <a href="?p={codebehind:GetNextPageIndex(.)}">
        <!-- ... -->
      </a>
      <!-- ... -->
    </xsl:template>
  </xsl:stylesheet>

Easy-peasy – no meddling about in web.config and a much tighter coupling between XSLT files and their .NET functions.

The XSLT codebehind functionality took me about half a day to code and include into all our projects. It has made it practically easier for developers to link functionality to XSLT files and conceptually easier to understand the placement of the XSLT functionality in the UI layer.

Standard
Sitecore, XSL

XslExtensions be gone! – part 1

One of the features of Sitecore which challenges correct development practise is XslExtensions (or XslHelpers as we call them). Good examples of XslExtensions are the Sitecore.Xml.Xsl.XslHelper and Sitecore.Xml.Xsl.SqlHelper classes (read more here).

The possibility of hooking in your own .NET functions into your XSLT code is a vital part of Sitecore, but the concept of XslExtensions brings a few problems to the party:

  • XslExtensions are actually a return to the world of functional programming and removes the concept of which context your code is running.
  • XslExtensions are loosly bound to where it is used. In practise it is difficult to know from where your code is being called – if it is called at all.
  • Hooking in XslExtensions requires changes to your web.config file. This is actually not a big problem, but it would be nice to be without.
  • XslExtension methods require specific types as input and output. Therefore if you introduce nice functionality in a XslExtension method, it is bound to the XSLT compliant types.
  • XslExtensions lies on the border between the UI and the business logic tier of your application. Therefore XslExtensions can often be cluttered with methods which replicate and port functionality in your business logic to XSLT compliancy, or actually introduces new business logic functionality.

A good example of this last two points are Sitecore.Xml.Xsl.XslHelper.HasBaseTemplate() method, which is actually the only place in the Sitecore API where you can query whether an Item derives from a given template. This means that if you need this code in ASP.NET, you need to replicate the functionality in your own classes or – even worse – call Sitecore.Xml.Xsl.XslHelper.HasBaseTemplate() from your ASP.NET codebehind.

So, what can we do to try to enforce good development practise while still maintaining the essential functionality of exposing .NET methods to XSLT’s? I’ll get back to that in part two of my post.

Standard
Sitecore, XSL

Reference to templates

The last couple of weeks I have stumbled on several people referring to a specific template when developing XSLT’s or in some other code. Even Sitecores standard XSL does it:

<xsl:variable name=”home” select=”$sc_item/ancestor-or-self::item[@template=’site root’]” />

It is fine that they finally removed the hardcoded path to /sitecore/content/home, but in my opinion the reference to the specific template ‘site root’ is still bad practice.

When developing a site one should take into account, that the site can be expanded, altered or duplicated later on. In the process of doing so, it is likely that a new template is created, which then inherits from the original template (in this case the ‘site root’). If this is the case all your renderings will stop working, just because you wanted to add a field, change a rendering or in any other way alter the original template.
Instead you should test whether an item is of a given ‘type’ – testing if the template the item is based on is equal to or derives from a specific template. This functionality has even been included in the XSLHelper class in Sitecore 6. Now it is possible to do the test like this:

<xsl:variable name=”home” select=”$sc_item/ancestor-or-self::item[sc:IsItemOfType(‘site root’,.)]”/>

Standard