I’ve been getting into some pretty advanced scenarios with WCF where we need to precisely shape the XML on the wire and the schema’s that are produced by svcutil.exe. Trust me, the moment you start using WCF for exposing services to Java clients, those Java clients are really going to care how the XML looks. WCF makes .NET-to-.NET very easy, and .NET-to-Java doable, but I wouldn’t call it easy. For vanilla scenarios (simple contracts with simple types and basic complex types), it’s pretty easy. The hooks exposed by WCF for shaping the XML are pretty poor in my opinion.
IXmlSerializable is your friend when it comes to shaping the xml on the wire. It will work with the Data Contract Serializer in WCF, but if you google around trying to find info on people using IXmlSerializable, you’ll quickly get confused. I wanted to hand-roll how my entity serializes, and this interface is very straightforward for that. For shaping the schema, it’s not straight forward in the slightest. Below are several lessons I learned over a number of days while working with IXmlSerialiable:
GetSchema is not what you use to shape the schema!
I don’t know what this method is for, but it’s not what it sounds like. Everything you’ll find on google is people returning null. If you programmatically construct an XmlSchema instance, and return it from GetSchema(), it won’t be used. As far as I can tell, the Data Contract Serializer never calls this method. Instead, you have to decorate your class with the XmlSchemaProvider attribute and specify the static method that should be called for shaping the schema. This method should accept an argument of type XmlSchemaSet and in that method is where you should construct your schema, and add it to the XmlSchemaSet. It appears the method you implement must return either an instance of XmlQualifiedName or any derivative of XmlSchemaObject (i.e. XmlSchemaComplexType or XmlSchemaSimpleType). Here’s an example:
[XmlSchemaProvider("ConstructSchema")]
public class Product : IXmlSerializable
{
private bool _deserializing;
protected object _data;
public virtual object Data
{
get { return _data; }
set { _data = value; }
}
public static XmlQualifiedName ConstructSchema(XmlSchemaSet schemaSet)
{
const string theNamespace = "http://webservices.kellybrownsberger.com/1.0";
XmlSchema schema = new XmlSchema
{
TargetNamespace = theNamespace,
ElementFormDefault = XmlSchemaForm.Qualified
};
schemaSet.Add(schema);
return new XmlQualifiedName("Product", theNamespace);
}
public XmlSchema GetSchema()
{
throw new NotImplementedException("Not implemented - the XMmlSchemaProvider attribute specifies calling the static GetSchema method instead");
}
public void ReadXml(XmlReader reader)
{
}
public void WriteXml(XmlWriter writer)
{
}
}
Shaping Schema using a read-in xml string
This is handy for two things in my mind – readability and code-gen. People tend to like looking at a schema in an xml representation. It’s more error prone, and not refactor-tool-friendly, but it’s more readable to the masses I think. Secondly, I like to code-gen my contracts in batch (using T4). Typically, I’ll define some XML grammar (DSL if you will), that describes my entities. I can then gen them all and make sure all of the correct attributes are applied. This might seem silly, but in large systems with interoperability being a top-priority, keeping all of your DataContract and DataMember attributes correct (one forgotten or botched namespace declaration can hose our consumers) is a bit daunting. When you get into the more complex things like contract versioning, and types implementing IXmlSerialable, it’s very daunting. Being able to code-gen this stuff has a lot of advantages. Reading in XML Schema as text is code-gen friendly as it can easily be captured as part of the DSL/grammar I mentioned, and the necessary code can be generating quickly and easilyand because your generating it, it’s less error prone.
Example:
public static XmlQualifiedName ConstructSchema(XmlSchemaSet schemaSet)
{
const string theNamespace = "http://webservices.kellybrownsberger.com/1.0";
StringBuilder builder = new StringBuilder();
builder.AppendLine(string.Format("<xs:schema xmlns:tns=\"{0}\" elementFormDefault=\"qualified\" targetNamespace=\"{0}\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">", theNamespace));
builder.AppendLine("<xs:complexType name=\"Product\">");
builder.AppendLine("<xs:sequence minOccurs=\"1\" maxOccurs=\"1\">");
builder.AppendLine("<xs:any />");
builder.AppendLine("</xs:sequence>");
builder.AppendLine("</xs:complexType>");
builder.AppendLine("</xs:schema>");
XmlSchema schema = XmlSchema.Read(new StringReader(builder.ToString()), null);
schemaSet.Add(schema);
return new XmlQualifiedName("Product", theNamespace);
}
Shaping Schema programmatically
It took me longer than I care to admit to figure out how to do this correctly (it took me forever to figure out that I needed to sequence to the XmlSchemaComplexType’s Particle property). Here’s an example:
public static XmlSchemaComplexType ConstructSchema(XmlSchemaSet schemaSet)
{
const string theNamespace = "http://webservices.kellybrownsberger.com/1.0";
XmlSchema schema = new XmlSchema
{
TargetNamespace = theNamespace,
ElementFormDefault = XmlSchemaForm.Qualified
};
XmlSchemaComplexType type = new XmlSchemaComplexType { Name = "Product" };
schema.Items.Add(type);
XmlSchemaSequence sequence = new XmlSchemaSequence {MinOccurs = 1, MaxOccurs = 1};
sequence.Items.Add(new XmlSchemaAny());
type.Particle = sequence;
schemaSet.Add(schema);
return type;
}