Craig Rowe

Techlead / Developer

12th October 2008

.NET objects in XML

Many people use XSL functions to enhance the abilities of their XML transforms. Although not everyone may be aware of a nice (and simple) way of achieving this.

XSL provides a large number of ways to manipulate and display data through constructs such as for-each, apply-templates, choose/when etc. However this does not always cover all possible requirements of a transform. Sometimes there is a need to run code, and this is often performed in the following way:

              <?xml version="1.0" encoding="utf-8"?>
              <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                  xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl cargowire"
                  xmlns:ms="urn:schemas-microsoft-com:xslt" xmlns:cargowire="http://cargowire.net/XSL">

                  <ms:script language="C#" implements-prefix="cargowire">
                  <![CDATA[
                      public string MakePointlessStatement() {
                          // This could actually be done without code but what the hey...
                          return "Hello World!";
                      }
                  ]]>
                  </ms:script>

                  <xsl:template match="/">
                      <xsl:value-of select="cargowire:MakePointlessStatement()" />
                  </xsl:template>

              </xsl:stylesheet>
          
fig. 1.0

The problem is that this not only looks and feels messy but mixes C# code (incidently this can be a number of languages… javascript and vb for example) with XSL files leading to a loss of intellisense in Visual Studio, less ease of deploying with your usual common code libraries and less ease of integration with your existing classes.

So what's the better way?

Extension Objects

Luckily for us (and with a quick look at the first intellisense item on an XsltArgumentList instance) .net enables us to pass in an object instance…

First we need a class that might be useful to have inside an XSL document.

              using System;
              using System.Web;

              namespace ExtensionObjects
              {
                  public class XslWebPage
                  {
                      public string GetExtensionlessAbsolutePath()
                      {
                          int length = HttpContext.Current.Request.Url.AbsolutePath.LastIndexOf('.');
                          return HttpContext.Current.Request.Url.AbsolutePath.Substring(0, (length > -1) ?
                              length : HttpContext.Current.Request.Url.AbsolutePath.Length);
                      }
                  }
              }
          
fig. 2.0

Ok, so you may find little use for such a class but it's used on this site as part of the URL creation within XSL generated pages. It's intended to act as a wrapper for the usual 'Page' object to enable any request type operations.

Lets pretend we have an <asp:Xml ID="myXML" runat="server" /> control in the markup of a page and a pre-populated XmlDocument instance called xDoc. The following code would create a new XsltArgumentList before adding an XslWebPage instance and binding the translated XML to the control.

              myXML.XPathNavigator = xDoc.CreateNavigator();

              System.Xml.Xsl.XsltArgumentList xtal = new System.Xml.Xsl.XsltArgumentList();

              xtal.AddExtensionObject("urn:XslWebPage", new XslWebPage());

              myXML.TransformSource = Server.MapPath("/transform.xsl");
              myXML.TransformArgumentList = xtal;
              myXML.DataBind();
          
fig. 2.1

To use this object within your transform is simple. First you must register the namespace to match what was specified when the object was added to the argument list.

              <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                  exclude-result-prefixes="msxsl cargowire page"
                  xmlns:ms="urn:schemas-microsoft-com:xslt"
                  xmlns:cargowire="urn:http://cargowire.net/XSL"
                  xmlns:page="urn:XslWebPage">
          
fig. 2.2

Then it can be used whenever needed:

              <xsl:value-of select="page:GetExtensionlessAbsolutePath()" />
          
fig. 2.3

Simple as that.

Conclusion

This is quite a simple addition to your XML/XSL armoury but one that I've found to be incredibly helpful particularly in avoiding an abundance of Xsl Parameters being passed here there and everywhere for things like URLs.

The plus side is that your XSL functions can be grouped into your common code libraries under a shared namespace (or even in one huge xsl utility if you want to go there!). You may even wish to go further and create a subclass of XslTransform overriding transform() to always submit your required utility classes as extension objects. This could have the added bonus of saving the hassle of maintaining xsl includes of functions files.

All article content is licenced under a Creative Commons Attribution-Noncommercial Licence.