Jul072009

XMLUrlResolver: Using Embedded XSLT Resources in C#

Over the last week or so, I have been searching for a method to properly include XSL files via xsl:include within a .NET embedded resource. Apparently, using GetManifestResourceStream() (via an Assembly) wasn't good enough (it wouldn't follow the xsl:includes, simply ignoring them). Luckily, I came across a (semi-)working solution over at Signs on the Sand. Now, the concepts behind this were right, it just wasn't working for my particular situation. Here's the scoop:

As I said before, using GetManifestResourceStream() is fine if you want to load a self-contained XSLT, which is what I was doing in most cases. However, when I started building more complex XSLT files, I wanted to break the redundant code down into sub-files (keys, headers, styles, etc). I thought xsl:include was going to save the day. Sadly, this didn't work. The way Microsoft implemented their XSLT parser is that it does not follow xsl:include file path unless they are absolute. To get around this, they created the XmlUrlResolver class. This class can be given to the XslCompiledTransform object to override how it follows include paths (regardless of whether they are embedded or actual URIs). Here is the implementation from Signs on the Sand:

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

namespace MyApp
{
    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));
        }
    }
}

The problem with this implementation was that it was trying to find the files on the desktop. So, if I had a tag that was <xsl:include href="MyApp.XmlToHtml.xslt">, where MyApp.XmlToHtml.xslt is the assembly path to the embedded resource, then it would actually look for C:\Documents and Settings\brandonmartinez\Desktop\MyApp.XmlToHtml.xslt. So, to fix this problem, I changed how it grabbed the assembly resource stream:

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

namespace MyApp
{
    public class EmbeddedResourceResolver : XmlUrlResolver
    {
        private Assembly _assembly;
        public EmbeddedResourceResolver()
        {
            _assembly = Assembly.GetExecutingAssembly();
        }

        public EmbeddedResourceResolver(Assembly assembly)
        {
            _assembly = assembly;
        }

        public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
        {
            return _assembly.GetManifestResourceStream(absoluteUri.Segments[absoluteUri.Segments.Length - 1]);
        }
    }
}

Basically, this changes two things. Firstly, it adds an additional constructor to accept a different assembly (in case it ever needs to be called from somewhere else). Secondly, it overrides the GetEntity method to extract the "filename" (which is actually my assembly path) from the absoluteUri, and loads that as a resource stream. After this small change, everything worked like a charm.

If you have any additional suggestions, please feel free to let me know. If you were like me, searching for hours on a solution, I hope this helps you! Good luck!