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.multiValuedAt开发者_Go百科tribute 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.
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).
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
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." ;-)
精彩评论