January 21, 2007

Loading XSLT stylesheets embedded into an assembly - the right way

Everybody knows that XSLT stylesheet can be embedded into an assembly by setting in Visual Studio its "BuildAction" property to "Embedded Resource". Such stylesheet then can be loaded using Assembly.GetManifestResourceStream() method.

But in fact, this is actually wrong way of loading embedded stylesheets, because once smarty-pants XML developer goes and breaks XSLT stylesheet into modules it suddenly stops working - xsl:import/xsl:include are not compatible with loading stylesheet via  Assembly.GetManifestResourceStream().

The right way of loading embedded stylesheets is via XmlResolver. Having custom XmlResolver loading stylesheet from an assembly solves the problem. And even better - you can use such resolver to load main XSLT stylesheet:

using (XmlReader doc = XmlReader.Create("books.xml"))
{
  XslCompiledTransform xslt = new XslCompiledTransform();
  EmbeddedResourceResolver resolver = 
    new EmbeddedResourceResolver();
  xslt.Load("Catalog.xslt",
    XsltSettings.TrustedXslt, resolver);
  xslt.Transform(doc, XmlWriter.Create(Console.Out));
}

The EmbeddedResourceResolver class can be as simple as:

using System;
using System.Xml;
using System.Reflection;
using System.IO;

namespace EmbeddedStylesheetSample
{
  public class EmbeddedResourceResolver : XmlUrlResolver
  {        
    public override object GetEntity(Uri absoluteUri, 
      string role, Type ofObjectToReturn)
    {
      Assembly assembly = Assembly.GetExecutingAssembly();
      return assembly.GetManifestResourceStream(this.GetType(), 
      Path.GetFileName(absoluteUri.AbsolutePath));
    }
  }
}

Obviously above implementation is way too simple. Particularly it loads resources embedded into the assembly where EmbeddedStylesheetSample class is defined. This can be parametrized so e.g. the resolver can accept assembly name and optional culture name and load class from an appropriate assembly. I think I need to generalize EmbeddedResourceResolver and include it into the Mvp.Xml library so we could just use it and not reinventing again and again.

...