I'm consuming a third-party service that requires XmlSerializerFormat contracts; I want to speed up startup by creating a serialization assembly, but Sgen.exe really doesn't like a particular construct in the schema that Xsd.exe spits out a nested array for.
The schema includes sequences of elements nested two levels deep like so:
Foo.xsd
<xs:schema targetNamespace="http://example.com" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://example.com" elementFormDefault="qualified">
<xs:element name="Foo" type="Foo"/>
<xs:complexType name="Foo">
<xs:sequence>
<xs:element name="List" type="FooList" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="FooList">
<xs:sequence>
<xs:element name="Item" type="FooListItem" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="FooListItem">
<xs:simpleContent>
<xs:extension base="xs:string"/>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
That is: a toplevel Foo
contains many FooList
s, and a FooList
contains many FooListItem
.
Running xsd /c Foo.xsd
produces the following:
Foo.cs
using System.Xml.Serialization;
[XmlType(Namespace="http://example.com")]
[XmlRoot(Namespace="http://example.com", IsNullable=false)]
public partial class Foo {
private FooListItem[][] listField;
[XmlArrayItem("Item", typeof(FooListItem), IsNullable=false)]
public FooListItem[][] List {
get {
return this.listField;
}
set {
this.listField = value;
}
}
}
[XmlType(Namespace="http://example.com")]
public partial class FooListItem {
private string valueField;
[XmlText]
public string Value {
get {
return this.valueField;
}
set {
this.valueField = value;
}
}
}
That is, no class for FooList
is present for some reason, instead there's just a nested array of FooListItem
s.
However, when I build this and run Sgen.exe on the resulting DLL using just sgen /keep obj\Debug\net461\Foo.dll
, this chokes on the following error messages:
error CS0030: Cannot convert type 'FooListItem[]' to 'FooListItem'
error CS0029: Cannot implicitly convert type 'FooListItem' to 'FooListItem[]'
(I'm using .NET 4.7 versions of Xsd.exe and Sgen.exe, I'm just targeting 4.6.1 for compatibility.)
Looking at the generated code, it chokes in the following method:
void Write3_Foo(string n, string ns, global::Foo o, bool isNullable, bool needType) {
if ((object)o == null) {
if (isNullable) WriteNullTagLiteral(n, ns);
return;
}
if (!needType) {
System.Type t = o.GetType();
if (t == typeof(global::Foo)) {
}
else {
throw CreateUnknownTypeException(o);
}
}
WriteStartElement(n, ns, o, false, null);
if (needType) WriteXsiType(@"Foo", @"http://example.com");
{
// THIS SEEMS TO BE THE ROOT CAUSE
global::FooListItem[][] a = (global::FooListItem[][])((global::FooListItem[][])o.@List);
if (a != null){
WriteStartElement(@"List", @"http://example.com", null, false);
for (int ia = 0; ia < a.Length; ia++) {
// ERROR IS REPORTED HERE
Write2_FooListItem(@"Item", @"http://example.com", ((global::FooListItem)a[ia]), false, false);
}
WriteEndElement();
}
}
WriteEndElement(o);
}
So it seems like Xsd.exe and Sgen.exe try to realize the pattern where an element has an explicit "list of X" child containing X items without creating a separate class for the list element, but only relying on the name of the serialized property to synthetise the intermediate element; and this breaks when the list element itself may be repeated.
Is there a way to work around this? Like force Xsd.exe to generate a class for the intermediate element? I suppose this might be an actual bug in Xsd.exe and Sgen.exe, but that won't really help me in a reasonable time frame.
As said above, this is a third-party service; I have absolutely no control over the schema and the less manual editing of generated code involved the better since my actual files are tens of thousands of lines long.
The error is on this line
From : [XmlArrayItem("Item", typeof(FooListItem), IsNullable=false)]
To :"[XmlArrayItem("Item", IsNullable = false)]
Here is sample of working code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("xs", "http://www.w3.org/2001/XMLSchema");
namespaces.Add("", "http://example.com");
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(FILENAME, settings);
XmlSerializer serializer = new XmlSerializer(typeof(Foo));
Foo foo = new Foo()
{
List = new FooListItem[][] {
new FooListItem[] {
new FooListItem() { Value = "abc"},
new FooListItem() { Value = "abd"},
new FooListItem() { Value = "abe"}
},
new FooListItem[] {
new FooListItem() { Value = "bbc"},
new FooListItem() { Value = "bbd"},
new FooListItem() { Value = "bbe"}
},
new FooListItem[] {
new FooListItem() { Value = "cbc"},
new FooListItem() { Value = "cbd"},
new FooListItem() { Value = "cbe"}
}
}
};
serializer.Serialize(writer, foo, namespaces);
}
}
[XmlType(Namespace = "http://example.com")]
[XmlRoot(Namespace = "http://example.com", IsNullable = false)]
public partial class Foo
{
private FooListItem[][] listField;
[XmlArrayItem("Item", IsNullable = false)]
public FooListItem[][] List
{
get
{
return this.listField;
}
set
{
this.listField = value;
}
}
}
[XmlType(Namespace = "http://example.com")]
public partial class FooListItem
{
private string valueField;
[XmlText]
public string Value
{
get
{
return this.valueField;
}
set
{
this.valueField = value;
}
}
}
}