XSLT can still dance

Harald Schult Ulriksen

or Message.ToString() is painfully slow.

XSLT may be on it's way out of Blink, but I'd say it can still prove its value and speed on the server.

Lately I've been working on a project with heavy use of WCF. And good use as well, with behaviours and user context flowing through from frontend through services.

One thing we use behaviours and message inspectors for is logging, and our log size is not trivial due to regulations and share site usage. However, at a few points in our application we do send files as byte streams, eating up quite a bit of unnecessary disk space. This will also slow down log aggregation and indexing.

When I inspected the old code it simply used the overloaded .ToString() on the System.Servicemodel.Channels.Message. With xml messages up to several megabytes in size I needed an efficient method for stripping our byte array, leaving the rest of the message intact for loging, including headers.

Having spent more than my fair share of time with XSL I decided to see if it was at all plausible to use XsltCompiledTransform to modify our wcf message. And boy was I up for a surprise. .ToString() on Message is just slow, mind numbingly slow. Sure, the first time our xsl transform runs, it too will consume cpu cycles, for about 270 milliseconds. But after that you will barely notice it.

Message.ToString() 170 milliseconds
Compiled xsl transform, first pass 270 milliseconds
Subsequent xsl transforms* 0.9 milliseconds

* On different messages

The implementation details

The xsl is quite simple, we just copy all the nodes we need. It is stored as an embedded resource in our dll for fast and easy access.

<xsl:stylesheet version="1.0"
                xmlns:b="http://tempuri.org/"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:a="http://schemas.datacontract.org/2004/07/BN.DataContracts"
                xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                >

  <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
  <xsl:strip-space elements="*" />
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="b:signedDocument" />
  <xsl:template match="signedDocument" />

</xsl:stylesheet>

The code applying the compiled transform looks like this, where ms is a memory stream and our _transform is a XsltCompiledTransform.

var navigator = messageBuffer.CreateNavigator();
navigator.MoveToRoot();
_transform.Transform(navigator.ReadSubtree(), null, ms);

This code is called from within our log behaviour. As a little bonus our transform runs directly on the messageBuffer created in the behaviour.

As a final note, remember to keep the compiled transform around to make sure it is not compiled more than once. If you have an IoC container, and are on board that the containers main task is controlling scope, you can store the compiles as named instances. One per xsl or per wcf message.

C#, xslt, messageinspector, wcf">
comments powered by Disqus