views:

53

answers:

2

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.

A: 

You could always change the type so that the web service returns something friendly to the wsdl processor. So for example, based on the example you gave, you should easily be able to convert that into an array of KeyValuePair<string, string> or something before returning it from the web service.

This is probably a better API anyways than exposing jagged arrays.

I can't accept that .NET is not able to consume this web service

Well, as my friend Mother Teresa says, "Life is a struggle, accept it." ;-)

Joel Martinez
I can't change the web service, it's part of the server software we bought. Which is also the reason I can't accept not being able to consume the web service, I simply have to work with what I've got.
michielvoo
+1  A: 

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

VinayC