views:

1479

answers:

2

Example Schema:

<complexType name="Dog">...</complexType>
<complexType name="Cat">...</complexType>

<complexType name="ArrayOfDog">
    <sequence>
        <element name="Dog" type="tns:Dog minOccurs="0" maxOccurs="unbounded" />
    </sequence>
</complexType>

<complexType name="Foo">
    <sequence>
        <element name="Bar" type="string"/>          
        <element name="Baz" type="anyType"/>
    </sequence>
</complexType>

Running this through .NET's wsdl.exe generates code similar to the following:

[System.Xml.Serialization.XmlIncludeAttribute(typeof(Dog[]))]

public partial class Dog { ... }

public partial class Cat { ... }

public partial class Foo {
    private string barField;
    private object bazField;
}

It appears that wsdl.exe is trying to be "smart" and realize that my ArrayOfDog is really just a wrapper type that can be encoded as a C# array. This works fine when ArrayOfDog is explicitly referenced in another data type. However, when ArrayOfDog is used polymorphically (e.g. as a substitution for xsd:anyType) this breaks. It appears to break because the .NET runtime knows nothing about the complexType named "ArrayOfDog" - it has basically thrown away this information in favor of just using native C# arrays.

Example XML document 1:

<Foo>
    <Bar>Hello</Bar>
    <Baz xsi:type="Cat">
        ...
    </Baz>
</Foo>

Example XML document 2:

<Foo>
    <Bar>Hello</Bar>
    <Baz xsi:type="ArrayOfDog">
        <Dog>...</Dog>
        <Dog>...</Dog>
    </Baz>
</Foo>

Document #1 is deserialized correctly by the runtime. I get an object of type Foo with correctly deserialized fields for Bar and Baz.

Document #2 is deserialized incorrectly by the runtime. I get an object of type Foo with a correctly deserialized field for Bar, but for the Baz field I get System.XML.XMLNode[]. My guess is because the runtime knows nothing about any type binding for an entity named "ArrayOfDog". You might think that the XmlInclude directive "XmlIncludeAttribute(typeof(Dog[]))" would handle this, but it doesn't appear to be working.

Has anyone come across this?

Is there an elegant solution here? The workaround I'm thinking of using is to wrap my "ArrayOf" type in another type and include that in the subsitution for the xsd:anyType.

+1  A: 

What do you want to start with? If you start with a type defined like this:

public partial class Foo
{
    private string _bar;

    private object[] _baz;

    public string Bar
    {
        get { return _bar; }
        set { _bar= value; }
    }

    [XmlArray("Baz")]
    [XmlArrayItem("Type1", typeof(Type1))]
    public object[] Baz
    {
        get { return _baz; }
        set { _baz= value; }
    }
}

Then you can serialize and de-serialize documents like your document #2.

Seems like you want to start with the WSDL and XSD. In that case, can you generalize your schema to look something like this:

<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
  <xs:element name="Foo" nillable="true" type="Foo" />
  <xs:complexType name="Foo">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="1" name="Bar" type="xs:string" />
      <xs:element minOccurs="0" maxOccurs="1" name="Baz" type="UntypedArray" />
    </xs:sequence>
  </xs:complexType>


  <xs:complexType name="UntypedArray">
    <xs:choice minOccurs="1" maxOccurs="unbounded">
      <xs:element name="Type1" type="Type1" minOccurs="1" maxOccurs="1"/>
      <xs:any namespace="##other" processContents="lax" minOccurs="1" maxOccurs="1"/>
    </xs:choice>
  </xs:complexType>


  <xs:complexType name="Type1" mixed="true">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="1" name="Child" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>

The above schema generates this code:

public partial class Foo {

    private string barField;

    private object[] bazField;

    /// <remarks/>
    public string Bar {
        get {
            return this.barField;
        }
        set {
            this.barField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlArrayItemAttribute("", typeof(System.Xml.XmlElement), IsNullable=false)]
    [System.Xml.Serialization.XmlArrayItemAttribute(typeof(Type1), IsNullable=false)]
    public object[] Baz {
        get {
            return this.bazField;
        }
        set {
            this.bazField = value;
        }
    }
}

If you want to include other types, then add elements to the the xsd:choice as appropriate.

Since you want to allow xsd:any within it, Foo.Baz is an object array. The programming model on it, requires you to test or cast each element with something like (foo.Baz[i] as Type1).

Cheeso
+1  A: 

I don't think this has anything to do with polymorphism. I think this is a bug in the XML Serializer, assuming that any type named "ArrayOfDog", containing a sequence of "Dog" is meant to represent a Dog[]. As a test of this theory, try changing the WSDL to use the name "BunchOfDogs" instead, and see if this changes the proxy code in the client.

If you want polymorphism in XML, then both ArrayOfDog, and Cat, will need to be extensions of the same base type (other than xsd:any). If that were the case, then I'd expect .NET to generate Baz as being of the base type.

Schemas with xsd:any cause problems just in general. There could be almost anything in there, and some combinations will simply not make sense.

You haven't said if this Java service is from Axis, or what version it is. I've seen Axis behave as though xsi:type were a substitute for a valid schema. Be careful with schemas that require "proper" use of xsi:type.

John Saunders
I agree. I think this is a bug in the .NET C# generator.If the .NET bindings generator wants to be cute and use native arrays for "ArrayOf" complexTypes that have a sequence with a single element with minOccurs=0 maxOccurs=unbounded, that's fine. However, it should not then disavow all knowledge of the "ArrayOf" complex type. If it sees that "ArrayOf" type in an "xsi:type=" attribute it is still responsible for unmarshalling it correctly.Please note though, that I am not using "xsd:any", rather "xsd:anyType". There is a difference (xsd:anyType maps to "Object" in both C# and java)
Eric
Re: "polymorphism". Probably not the best term, but I view the use of xsd:anyType as a way to get generic/polymorphic behavior out of XML. In terms of substitutability, it's no different than using type extensions. The benefit vs. xsd:extension is that my types don't all have to extend from a common ancestor.I agree though, if I had modeled "ArrayOfDog" using xsd:extension, .NET probably wouldn't have tried to apply its trick of forgetting about the type and just using native arrays all over the place.
Eric
@Eric: It thinks that another instance of the XML Serializer has generated "ArrayOfDog" from Dog[], so it's just doing the inverse. Please try the experiment of changing the name of the type - not as a workaround, but as a diagnostic.
John Saunders
I just tested renaming the type, as you suggested. It doesn't change the bindings generated by wsdl.exe. Their rule for shortcutting to native arrays must be based only the complexType containing a sequence of a single unbounded element (and not the name of the complexType itself). Another point - it's not "doing the inverse" correctly in the xsi:type case. i.e. it's not deserializing back a Dog[] from an element with xsi:type=ArrayOfDog - you just get XMLNode. IMO, this is a bug.
Eric
@Eric: Please report this bug at http://connect.microsoft.com/visualstudio/. Search first to see if this has been reported already. Once you've entered a bug report, please edit your question to include a link to the bug report. That way, those who find your question will be able to vote on how important they feel the bug is, and will be able to follow the progress. I'll be voting, and following...
John Saunders