posts - 76, comments - 26, trackbacks - 0

Hand-Shaping the XML Schema of your data contract using IXmlSerializable

 

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;
        }

Print | posted on Friday, April 10, 2009 11:14 AM | Filed Under [ .net development ]

Feedback

Gravatar

# re: Hand-Shaping the XML Schema of your data contract using IXmlSerializable

Kelly,

IXmlSerializable is confusing me when used with WebMethods. To illustrate, I created two product classes: SimpleProduct (which does not implement IXmlSerializable) and a ComplexProduct (which does use IXmlSerializable). I created a GetSimpleProduct (no parms, returns a SimpleProduct) and GetComplexProduct web method (no parms, returns a ComplexProduct).

I publish the web service and I add a reference to my web service in a test winForms app. My proxy class is generated for the web service and so far, so good.

I attempt to call both WebMethods from the winForm app:

ServiceReference1.Service1SoapClient c = new Test_Form.ServiceReference1.Service1SoapClient();

Test_Form.ServiceReference1.SimpleProduct simpleProduct = c.GetSimpleProduct();

XmlElement complexProductElement c.GetComplexProduct();

Anyone sending or receiving ComplexProducts from my web service has to deal directly with an XmlElement, instead of a ComplexProduct. Sending/receiving SimpleProducts uses the SimpleProduct class as defined in the proxy class.

Why?

I know this is a very basic question, but I've spent 8 hours on it already and I don't like solution!

Thanks in advance!

P.S. The reason we're using IXmlSerialization is that we have a few System.Generic.Collections.Dictionary objects in our base classes. We could take the time to rework this, but using IXmlSerialization was the work around we chose.
4/22/2009 1:20 PM | Allan Litchfield
Gravatar

# re: Hand-Shaping the XML Schema of your data contract using IXmlSerializable

Allan - I'm really sorry for the much delayed response. I accidentally switched off email notifications when toying with Subtext. I wasn't aware there was a comment sitting here waiting for me. It sounds like you needed immediate assistance, so maybe I'm way too late to make a difference, but I wanted to respond anyway.

I've never directly experienced what we're describing. This sounds like a case where the contract of the web service is not being projected correctly in WSDL/XSD and therefore the proxies being generated are screwed up. In the ASMX days we had a custom built proxy code generator, but I seem to remember reading about the stock generator doing what you described any time it hit something it couldn't understand.

My recommendation would be to start looking at the WSDL/XSD for your service and look for issues. If you're implementing IXmlSerializable, it's very likely the schema is not projected correctly. A tool that can help with this is soapUI (http://www.soapui.org/). You can point it to your URL and it will generate test proxies and allow you to execute the service in real time. If there's something invalid about your WSDL/XSD, it will probably tell you. It's built in Java, which is nice from an interoperability verification standpoint.

Also - If you'd like to post your WSDL here, I'd be happy to take a look at it. I might be able to spot something.

Again, sorry for the delayed response. I've got email notifications back up and running again so I'll be quicker to respond from now on :)
6/16/2009 7:27 AM | kellyb

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 7 and 3 and type the answer here:

Powered by: