Answered by:
Schema generation with xsd.exe and IXmlSerializable.GetSchema()

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 PMModerator -
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 PMModerator
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 PMModerator -
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 PMModerator -
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 PMModerator -
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