Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I fix the web reference proxy that Visual Studio generated to handle jagged arrays?

It seems there is a known bug in wsdl.exe, the tool that Visual Studio uses to generate web service proxies. With certain XSD schemas the tool will generate classes that can't be deserialized from the XML.

As far as I'm concerned that's unacceptable, but I don't know how to fix it.

I will describe my case in detail, hopefully somebody will be able to help me with it.

Schema

<!-- return type from the service operation -->
<xs:complexType name="listAssetsQueryResults">
    <xs:sequence>
        <xs:element name="assets" type="tns:asset" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
</xs:complexType>

<!-- a sequence of attributes -->
<xs:complexType name="asset">
    <xs:sequence>
        <xs:element name="attributes" type="tns:multiValuedAttribute" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
</xs:complexType>

<xs:complexType name="multiValuedAttribute">
    <!-- not relevant-->
</xs:complexType>

XML response from the webservice

A typical response according to this schema looks like this:

<assets-query-result>
    <assets>
        <attributes>
            <name>Keywords</name>
            <values>Desert</values>
        </attributes>
        <attributes>
            <name>Filename</name>
            <values>Desert.jpg</values>
        </attributes>
    </assets>
    <assets>...</assets>
    <assets>...</assets>
</assets-query-result>

Using the types in code

I would have expected to be able to use the CLR types like this:

result.assets[0].attributes[0].name

Instead, the generated type for the result looks like this:

[SerializableAttribute()]
public partial class listAssetsQueryResults {
    private multiValuedAttribute[][] assetsField;

    [XmlArrayAttribute(Form=XmlSchemaForm.Unqualified, IsNullable=true)]
    [XmlArrayItemAttribute("attributes", typeof(multiValuedAttribute), Form=XmlSchemaForm.Unqualified)]
    public multiValuedAttribute[][] assets {
        get { return this.assetsField; }
        set { this.assetsField = value; }
    }
}

Which doesn't even allow the serialization assembly to be generated!

Unable to convert type Portfolio.WebService.multiValuedAttribute to Portfolio.WebService.multiValuedAttribute[]

Fixing it

1 - Changing the type of the property and field

Now one of the fixes I found online is simply to remove one pair of brackets from the type of the generated property:

// No longer a jagged array, but this doesn't deserialize all data
public multiValuedAttribute[] assets;

That allows the serialization assembly to be built, and it runs without exceptions, except it doesn't serialize the data correctly, it 'skips' the list of assets and deserializes the attributes of the first assets element. So it's not a fix at all, because with this fix I can't consume the data. For 700+ assets it gives result.assets is equal to multiValuedAttribute[2] (the 2 elements are the name and keywords attributes of the first asset).

2 - Specifying the type of the XML-elements

The second thing I tried is to give the deserializer different instructions:

[XmlArrayItemAttribute("attributes", typeof(multiValuedAttribute[]), Form=XmlSchemaForm.Unqualified)]
public multiValuedAttribute[][] assets { ... }

So now I'm telling it that each element in the sequence is of type multiValuedAttribute[]. That's wrong because it's still looking at attributes elements, which are of type multiValuedAttribute (single, not an array). It does run however, but now the result.assets is equal to multiValuedAttribute[2][0] and I'm still not able to get to the data.

What's next?

I have no idea, which is why I wrote this. I can't accept that .NET is not able to consume this web service, because it has to.

like image 500
Michiel van Oosterhout Avatar asked Aug 06 '10 13:08

Michiel van Oosterhout


2 Answers

I think that you should define a separate Asset class that would have property of type multiValuedAttribute[]. So it would go something like

public class Asset
{
   public multiValuedAttribute[] attributes {get; set;}
}

public partial class listAssetsQueryResults {
    private Asset[] assetsField;

    public Asset[] assets {

Then you need to decorate Asset type, attributes & assets property with some combination of XmlElement/XmlArrayElement/XmlArrayItemElement attributes to get it working.

Needless to say, anytime you need to re-generate your proxy code, you have to re-apply above changes (perhaps you can make a batch script for that as a build action).

like image 178
VinayC Avatar answered Sep 28 '22 03:09

VinayC


This problem is very typical when consuming SOAP web services that were written in Java and then consumed by WCF, C#, .Net, etc. where jagged arrays (arrays of arrays) are used. VinayC's post helped, but here is some more explicit code for example that may help others who encounter this problem.

Note that these classes are abbreviated. Your WSDL generated code will surely look more complex.

public partial class assests{
  private multiValuedAttribute[] attributesField;
  [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
  public multiValuedAttribute[] attributes{
    get {return this.attributesField;}
    set {this.attributesField = value;}
  }
}

public partial class listAssetsQueryResults{
  private assests[] assetsField;
  [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
  public assets[] assets{
      get {return this.assetsField;}
      set {this.assetsField = value;}
  }
}

The key here is to create a new 'assets' class that is a link between multiValuedAttribute and listAssetsQueryResults classes, and then serialize the field using XmlElementAttribute. A good way to do this in your own projects is to start by copying and pasting the class that is analogous to the multiValuedAttribute class (although that class implementation is not shown here). Then, just rename the copied multiValuedAttribute class as 'assets' and alter the implementation so that it instead works as a link between the two. Remove the extra [] in the statements that contain [][] in the listAssetsQueryResults class. Serialize using XmlElement instead of XmlArrayItem.

Of course, different services have different requirements. The Fiddler application (http://fiddler2.com/) can really help inspect the serialization to make sure you're getting it right. Fiddler really really helped me. Here is some additional reading that helped me as well: http://msdn.microsoft.com/en-us/library/2baksw0z.aspx

like image 24
scradam Avatar answered Sep 28 '22 03:09

scradam