locked
Schema generation with xsd.exe and IXmlSerializable.GetSchema() RRS feed

  • Question

  • Hi, I'm trying to implement IXmlSerializable.GetSchema() and to get xsd.exe to put the schema definition in its parent's Xml schema definition, like when you just don't implement IXmlSerializable.

    i.e. If I do the following classes :

    public class SomeClass : IXmlSerializable
    {
      ...
      public XmlSchema GetSchema()
      {
        XmlSchema schema = new XmlSchema();
        // Construct the schema
        return schema;
      }
    }

    [Serializable()]
    public class ParentClass
    {
      private SomeClass someInstance;

      public SomeClass SomeInstance
      {
        get { return someInstance; }
        set { someInstance = value; }
      }
    }

    ...and run xsd.exe on an ParentClass, I'd get two files :
    - schema0.xsd which contains the ParentClass schema
    - schema1.xsd which contains the SomeClass schema

    And in schema0.xsd, the following block for SomeInstance :
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="xs:schema" />
        <xs:any ... />
      </xs:sequence>
    </xs:complexType>

    As opposed to, if SomeClass did not implement IXmlSerializable, a single schema0.xsd which has everything in it.

    Problems associed to that :
    - I have to specify a unique target namespace for each class that implements IXmlSerializable, even if it's not [Serializable()] as-is
    - I can't use common complexTypes from the parent schema definition... or I don't know how
    - It's annoying? :)

    My question is : is it possible to implement IXmlSerializable and still have a single XSD file?
    Thursday, July 20, 2006 3:43 PM

Answers

  • The answer to your question is “Yes”, but you cannot use GetSchema() method for this.

    You need to work with [XmlShcemaProvider] attribute on IXmlSerializable implementation.

    This IXmlSerializable extension is new in V2.0.

    Let me know if you need a sample.

     

    Thanks,

    Elena Kharitidi

    Friday, July 21, 2006 9:30 PM
    Moderator
  • Here is the sample I promised.  It generating schemas for

        public class SampledList<T > : IXmlSerializable {}

     

    While I was working on it I found a bug in the IXmlSerializable implementation, the code in the post works around the issue.

     

    Thanks,

    Elena

     

    Compile the following into a dll, and run xsd.exe on it:

     

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;

    [XmlType(Namespace="http://my/SampledList/types")]
    public class xxx {
        public int id;
    }

    public class Test {
        public SampledList<xxx> list;
    }

    [XmlSchemaProvider("ProvideSchema")]
    public class SampledList<T> : List<T>, IXmlSerializable
    {
        static string Namespace = "http://my/SampledList/types";
        public static XmlQualifiedName ProvideSchema(XmlSchemaSet xss)
        {
            XmlReflectionImporter importer = new XmlReflectionImporter();
            XmlSchemas schemas = new XmlSchemas();
            XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
            XmlTypeMapping itemMapping = importer.ImportTypeMapping(typeof(T));
            exporter.ExportTypeMapping(itemMapping);

            XmlSchema schema = schemas[Namespace];
            if (schema == null)
            {
                schema = new XmlSchema();
                schema.TargetNamespace = Namespace;
                schema.ElementFormDefault = XmlSchemaForm.Qualified;
                xss.Add(schema);
            }

           
            foreach (XmlSchema s in schemas)
            {
                xss.Add(s);
                if (s.TargetNamespace != null && s.TargetNamespace.Length > 0 && s.TargetNamespace != Namespace)
                {
                    XmlSchemaImport import = new XmlSchemaImport();
                    import.Namespace = s.TargetNamespace;
                    schema.Includes.Add(import);
                }
            }

            // now add the schema type for the list
            //   <xs:complexType name="sampledList">
            //     <xs:sequence maxOccurs="unbounded">
            //       <xs:element name="item" type=".." />
            //     </xs:sequence>
            //   </xs:complexType>

            XmlSchemaComplexType ct = new XmlSchemaComplexType();
            XmlSchemaSequence sequence = new XmlSchemaSequence();
            sequence.Items.Add(XmlElement("item", new XmlQualifiedName(itemMapping.XsdTypeName, itemMapping.XsdTypeNamespace)));
            sequence.MaxOccurs = Decimal.MaxValue;
            ct.Particle = sequence;
            // UNDONE: need to generate unique name for the type;
            ct.Name = string.Format("sampledList_of_{0}",  itemMapping.XsdTypeName);
            schema.Items.Add(ct);
            xss.Reprocess(schema);

            WorkaroundMergeIssue(schemas);

            return new XmlQualifiedName(ct.Name, Namespace);
        }

        static XmlSchemaElement XmlElement(string name, XmlQualifiedName type)
        {
            XmlSchemaElement e = new XmlSchemaElement();
            e.Name = name;
            e.SchemaTypeName = type;
            return e;
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            // UNDONE: Read instance
        }

        public void WriteXml(XmlWriter writer)
        {
            // UNDONE: Write instance
        }

        /// <devdoc>
        ///    <para>
        ///    This methods works around and issue with merging user schemas and
        ///    schemas generated by IXmlSerializable implementation.
        ///    The schema Merge method does not works properly on compiled
        ///    schemas, due to a bug in Framework.
        ///    The symptom of the problem is the following exception:
        ///   
        ///    Error: There was an error processing 'ixmllist.dll'.
        ///    - Cannot merge schemas with targetNamespace='.. '.
        ///         Several mismatched declarations were found:
        ///         Schema item 'complexType' named '..' from namespace '..'.
        ///   
        ///    Followed by two identical serialized instances of the
        ///    complextype in question.
        ///    To workaround the problem the IXmlSerializable has to set
        ///    inner element’s parent to null.
        ///
        ///    </para>
        /// </devdoc>
        static void WorkaroundMergeIssue(XmlSchemas schemas)
        {
            foreach (XmlSchema schema in schemas)
            {
                foreach (XmlSchemaObject o in schema.Items)
                {
                    XmlSchemaComplexType complexType = o as XmlSchemaComplexType;
                    if (complexType != null)
                    {
                        XmlSchemaSequence seq = complexType.Particle as XmlSchemaSequence;
                        if (seq != null)
                        {
                            foreach (XmlSchemaObject item in seq.Items)
                            {
                                item.Parent = null;
                            }
                        }
                    }
                }
            }
        }
    }

    Monday, July 31, 2006 8:19 PM
    Moderator

All replies

  • The answer to your question is “Yes”, but you cannot use GetSchema() method for this.

    You need to work with [XmlShcemaProvider] attribute on IXmlSerializable implementation.

    This IXmlSerializable extension is new in V2.0.

    Let me know if you need a sample.

     

    Thanks,

    Elena Kharitidi

    Friday, July 21, 2006 9:30 PM
    Moderator
  • Thanks for the answer!
    But after some quick experimentation, yes I'd like a sample... because I've tested the sample on http://msdn2.microsoft.com/en-us/library/system.xml.serialization.xmlschemaproviderattribute.aspx and using xsd.exe doesn't call the method I specify on XmlSchemaProvider and the NotImplementedException thrown by GetSchema() is not caught. Returning null doesn't help either.

    Edit : Here's what my class looks like, actually. You should know that I'm not implementing ISerializable or declaring a [Serializable()] because it's not meant to be serialized on its own... only part of an aggregated document.
    ________

        [XmlSchemaProvider("ProvideSchema")]
        public class SampledList<T, EnumType> : InternalSampledList<T, EnumType>, IXmlSerializable
            where T : new()
            where EnumType : struct
        {
            public SampledList(IEnumerable<T> collection) : base(collection) { }
            public SampledList() : base() { }

            // (cut code)

            #region IXmlSerializable Members

            public static XmlQualifiedName ProvideSchema(XmlSchemaSet xs) {
                System.Diagnostics.Debugger.Break();
                return null;
            }

            public XmlSchema GetSchema()
            {
                return null;
            }

            public void ReadXml(XmlReader reader)
            {
                // Reads stuff
            }

            public void WriteXml(XmlWriter writer)
            {
                // Write stuff
            }

            #endregion
        }
    Monday, July 24, 2006 1:36 PM
  • Sorry for bumping, but please don't forget about this... I'm quite out of resources.
    Tuesday, July 25, 2006 8:42 PM
  • Waiting still... Sorry again.
    Friday, July 28, 2006 4:53 PM
  • Sorry for the delay: I will get back to you on Monday.

    Thanks, Elena

    Saturday, July 29, 2006 10:48 PM
    Moderator
  • Here is the sample I promised.  It generating schemas for

        public class SampledList<T > : IXmlSerializable {}

     

    While I was working on it I found a bug in the IXmlSerializable implementation, the code in the post works around the issue.

     

    Thanks,

    Elena

     

    Compile the following into a dll, and run xsd.exe on it:

     

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;

    [XmlType(Namespace="http://my/SampledList/types")]
    public class xxx {
        public int id;
    }

    public class Test {
        public SampledList<xxx> list;
    }

    [XmlSchemaProvider("ProvideSchema")]
    public class SampledList<T> : List<T>, IXmlSerializable
    {
        static string Namespace = "http://my/SampledList/types";
        public static XmlQualifiedName ProvideSchema(XmlSchemaSet xss)
        {
            XmlReflectionImporter importer = new XmlReflectionImporter();
            XmlSchemas schemas = new XmlSchemas();
            XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
            XmlTypeMapping itemMapping = importer.ImportTypeMapping(typeof(T));
            exporter.ExportTypeMapping(itemMapping);

            XmlSchema schema = schemas[Namespace];
            if (schema == null)
            {
                schema = new XmlSchema();
                schema.TargetNamespace = Namespace;
                schema.ElementFormDefault = XmlSchemaForm.Qualified;
                xss.Add(schema);
            }

           
            foreach (XmlSchema s in schemas)
            {
                xss.Add(s);
                if (s.TargetNamespace != null && s.TargetNamespace.Length > 0 && s.TargetNamespace != Namespace)
                {
                    XmlSchemaImport import = new XmlSchemaImport();
                    import.Namespace = s.TargetNamespace;
                    schema.Includes.Add(import);
                }
            }

            // now add the schema type for the list
            //   <xs:complexType name="sampledList">
            //     <xs:sequence maxOccurs="unbounded">
            //       <xs:element name="item" type=".." />
            //     </xs:sequence>
            //   </xs:complexType>

            XmlSchemaComplexType ct = new XmlSchemaComplexType();
            XmlSchemaSequence sequence = new XmlSchemaSequence();
            sequence.Items.Add(XmlElement("item", new XmlQualifiedName(itemMapping.XsdTypeName, itemMapping.XsdTypeNamespace)));
            sequence.MaxOccurs = Decimal.MaxValue;
            ct.Particle = sequence;
            // UNDONE: need to generate unique name for the type;
            ct.Name = string.Format("sampledList_of_{0}",  itemMapping.XsdTypeName);
            schema.Items.Add(ct);
            xss.Reprocess(schema);

            WorkaroundMergeIssue(schemas);

            return new XmlQualifiedName(ct.Name, Namespace);
        }

        static XmlSchemaElement XmlElement(string name, XmlQualifiedName type)
        {
            XmlSchemaElement e = new XmlSchemaElement();
            e.Name = name;
            e.SchemaTypeName = type;
            return e;
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            // UNDONE: Read instance
        }

        public void WriteXml(XmlWriter writer)
        {
            // UNDONE: Write instance
        }

        /// <devdoc>
        ///    <para>
        ///    This methods works around and issue with merging user schemas and
        ///    schemas generated by IXmlSerializable implementation.
        ///    The schema Merge method does not works properly on compiled
        ///    schemas, due to a bug in Framework.
        ///    The symptom of the problem is the following exception:
        ///   
        ///    Error: There was an error processing 'ixmllist.dll'.
        ///    - Cannot merge schemas with targetNamespace='.. '.
        ///         Several mismatched declarations were found:
        ///         Schema item 'complexType' named '..' from namespace '..'.
        ///   
        ///    Followed by two identical serialized instances of the
        ///    complextype in question.
        ///    To workaround the problem the IXmlSerializable has to set
        ///    inner element’s parent to null.
        ///
        ///    </para>
        /// </devdoc>
        static void WorkaroundMergeIssue(XmlSchemas schemas)
        {
            foreach (XmlSchema schema in schemas)
            {
                foreach (XmlSchemaObject o in schema.Items)
                {
                    XmlSchemaComplexType complexType = o as XmlSchemaComplexType;
                    if (complexType != null)
                    {
                        XmlSchemaSequence seq = complexType.Particle as XmlSchemaSequence;
                        if (seq != null)
                        {
                            foreach (XmlSchemaObject item in seq.Items)
                            {
                                item.Parent = null;
                            }
                        }
                    }
                }
            }
        }
    }

    Monday, July 31, 2006 8:19 PM
    Moderator
  • Excellent!!
    I've tested it in my implementation and it looks like it works perfect. Thanks a lot for your time, Elena...

    Edit : Err... In fact, the only problem I see is that the "xxx" element in your implementation is available from the root, even if I specify to xsd.exe to serialize only "Test". Is there a way to prevent this? I've played with the code a bit, didn't find anything.

    Edit 2 : And an additional note, if you have the following structure :
    ______________

    public class Root
    {
        public Test Test;
    }

    public class Test
    {
        public TestList List;
    }

    public class TestList : SampledList<ListItem> { }

    public class ListItem
    {
        public int id;
    }

    [XmlSchemaProvider("ProvideSchema")]
    public class SampledList<T> : List<T>, IXmlSerializable
    {
    ...
    }
    ______________

    ..., the xsd.exe app does not find the [XmlSchemaProvider] attribute and will not serialize the schema using the method. Looks like a bug, but its workaround is easy; just add a redirect on the inheriting class :
    ______________

    [XmlSchemaProvider("ProvideSchema")]
    public class TestList : SampledList<ListItem>
    {
        public new static XmlQualifiedName ProvideSchema(XmlSchemaSet xss)
        {
            return SampledList<ListItem>.ProvideSchema(xss);
        }
    }
    ______________

    And everything works. :)
    Tuesday, August 1, 2006 3:44 PM