Transforming Messages with XSLT

| | Comments (0) | TrackBacks (0)

We are currently building a handful of entity services using WCF.  Each is built around a collection of stored procedures that receive and return XML.  The other day I got to thinking: Why go through the trouble of (de)serializing XML into objects?  Why not read the SOAP body directly, use XSLT to convert it into the expected form, pass it to the stored procedures, and transform the results back into a SOAP message?  This would surely increase performance by avoiding unnecessary (de)serialization. In our case, the performance hit was worth the cost because the services were doing more than simply transforming XML; however, I was still curious: How would one go about performing such a transformation at the SOAP-message-level in WCF?

There is a reference on MSDN that provides some good background.  After reading it, I learned that any service wanting to work at this level had to specify that its operations receive and (if applicable) return Message objects.  With one of these in hand, an XmlReader can be instantiated by calling its GetReaderAtBodyContents method.  In turn, this can be passed to the XSLT processor.  If a return value is required, XML can be written to the resulting SOAP message using the Message class's static CreateMessage method.  Here is a short example:

        public Message Transform(Message transformRequest)
    {
        using (XmlReader reader =
            transformRequest.GetReaderAtBodyContents())
        {
            StringBuilder buf = new StringBuilder();

            using (XmlWriter writer = XmlWriter.Create(buf, settings))
            {
                MessageVersion version =
                    OperationContext.Current.IncomingMessageVersion;

                xslt.Transform(reader, writer);
                Debug.WriteLine(buf);
        

                return Message.CreateMessage(version, "TransformResponse",
                    new BodyWriter(buf));
            }
        }
    }

In this sample, the service gets an XmlReader over the incoming SOAP message's body, instantiates an XmlWriter that will write to a given buffer, transforms the XML of the reader putting the results in the writer, and creates a SOAP response message with the contents of the buffer placed in its body.

The astute reader may have notices something odd about that snippet.  It instantiates a BodyWriter object which is abstract.  That true, but the one created in the code above isn't the one in System.ServiceModel.Channels; it's one of my own creation.  To see all of the details, check out the whole sample.

After performing this little experiment, my curiosity was temporarily satified; however, I still had a couple of questions about all this:

  1. The XSLT stylesheet can't be written a priori unless the shape of the input XML is well known.  The XML returned by our stored procedures conforms to an XML Schema, so the shape of that input is consistent and predictable.  What about the body of the SOAP message, however?  To insure that it too conforms to a known shape, ideally, it would be contractually obligated to conform to a certain XML Schema.  How though does one go about creating message contracts starting with an XML Schema?  I know how to do this with data contracts but not message contracts.
  2. What if the operation contracts of a service expect explicit arguments rather than Message objects and the interface is already in use?  Is there any way to create a new service type that implements the contract and is able to work at the message-level to perform the types of transformations described above?
  3. Can the simple example be implemented without the little nested BodyWriter class?  It isn't providing much besides bloat.
  4. How could the sample be written using a streaming model rather than a buffered approach?
While doing this experiment, I also tried out the new debugging capability of Visual Studio 2008 which allows one to step from a CLR language into XSLT.  This was achieved by passing a flag to the XslCompiledTransform's constructor and stepping into the processor's Transform method.  Pretty fancy.