High-performance XML (V): Increase performance on message-based web services by avoiding XmlDocument creation when using XmlSerializer
Note: this entry has moved.
In a previous post,
I discussed the way coarse-grained web service interfaces
can be used in a strongly typed way
by resorting to the xsd.exe or a
cool custom tool. The only problem with that approach is that in order to
communicate with the service, you need to pass an
XmlNode, when you actually have an instance of
an object. People will usually write code like the following
to call the service:
// Customer is an XmlSerializer-friendly class
Customer cust = new Customer("Daniel", "Cazzulino", new DateTime(1974, 4, 9));
// "Convert" the object to an XmlNode
XmlSerializer ser = new XmlSerializer(typeof(Customer));
XmlNode customerNode;
using (MemoryStream mem = new MemoryStream())
{
ser.Serialize(mem, cust);
mem.Position = 0;
XmlDocument doc = new XmlDocument();
doc.Load(mem);
customerNode = doc.DocumentElement;
}
service.Execute(customerNode);
All the code to perform the XML serialization is so standard
and repetitive that most developers will already have some
sort of helper class with a single method that receives an
objects and gives them back an XmlNode "view"
of it.
There are several problems with this approach, though, all of them affecting the performance of your application:
-
Even if the information of the object is already in memory
in the typed class, a new "copy" is generated through the
XmlSerializer, consuming more memory as a consequence (the MemoryStream). - XML must be parsed from the stream, now consuming CPU processing
-
An
XmlDocumentwith its entire object graph is built from the parsing process (that happens automatically when you call Load on it), which is now a different "view" of your object, but in terms of the DOM, which you will not use at all in your application
Interestingly, none of that is necessary, if you know the internal workings of the web service infrastructure used on the client-side proxy, which doesn't differ much from the one used on the server (asmx) side.
The XmlNode you pass to the service method call
must be serialized and embedded as the body of the SOAP
message to send to the server. Just like I explained a while
back that it happens on the server side, the infrastructure
will call XmlNode.WriteTo(XmlWriter w) passing
a writer to emit content to the body of the SOAP message. So
all we need to do is hold a "faked"
XmlNode until that method is called, and
serialize the object graph directly to that writer instead:
public class ObjectNode : XmlElement
{
private object serializableObject;
// All arguments for the base class are irrelevant.
public ObjectNode(object serializableObject)
: base("mvp", "xml", string.Empty, new XmlDocument())
{
this.serializableObject = serializableObject;
}
public override void WriteTo(XmlWriter w)
{
XmlSerializer ser = new XmlSerializer(serializableObject.GetType());
ser.Serialize(w, serializableObject);
}
}
Now your client code will look like the following:
// Customer is an XmlSerializer-friendly class
Customer cust = new Customer("Daniel", "Cazzulino", new DateTime(1974, 4, 9));
service.Execute(new XmlObjectNode(cust));
Now your client application will never consume resources for
copying the object to a stream, parsing and loading an
XmlDocument just to make a web service call.
This will result in a significant performance increment,
which will depend on the kind of object graphs you usually
serialize.
For consistency with the approach I explained before for
increasing the performance in this kind of scenario on the
server side, the
Mvp.Xml project
implements this as another overload on the
XmlNodeFactory class, so that your code is
completely isolated from the implementation (which could
even be based on the inefficient one explained before).
Consuming code is equally simple, though:
service.Execute(XmlNodeFactory.Create(cust));
The internal implementation is just another overload returning a custom SerializableNode type:
public class XmlNodeFactory
{
public static XmlNode Create(object serializableObject)
{
return new ObjectNode(serializableObject);
}
….
private class ObjectNode : SerializableNode
{
private object serializableObject;
public ObjectNode() {}
public ObjectNode(object serializableObject)
{
this.serializableObject = serializableObject;
}
public override void WriteTo(XmlWriter w)
{
XmlSerializer ser = new XmlSerializer(serializableObject.GetType());
ser.Serialize(w, serializableObject);
}
}
You can download the Mvp.Xml project (binary and sources) from SourceForge.
This post is part of the XML Performance series.