In .NET XML deserialization, how can I allow polymorphic use of Array types?

我们两清 提交于 2019-12-29 08:01:28

问题


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:


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.




回答2:


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">
  <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).



来源:https://stackoverflow.com/questions/1007626/in-net-xml-deserialization-how-can-i-allow-polymorphic-use-of-array-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!