Simon Fell > Its just code > August 2005

Monday, August 15, 2005

Further investigation somewhat disappointingly suggests that .NET 2.0 will have the same problem, it does appear to be fixed in Indigo though.

Thursday, August 11, 2005

Never a dull day looking at SOAP interop problems (plenty of frustrating ones though). Ran into some interesting behavior in .NET 1.1 when it gets faced with a WSDL that contains headers that are simple types (such as you might do if you were structuring something like WS-Addressing does, with lots of top level soap headers that have simple values). First off, a simple baseline, here is test_noheaders.wsdl, describes a simple services that takes 2 ints, and returns an int, has no headers, everything works as you've come to expect. Now, for the first curve ball, test_1header.wsdl is the exact same WSDL but defines an additional header, thusly

<s:element name="ValidFrom" type="s:dateTime"/>
Along with the relevent entries in the messages and binding etc. Now running wsdl.exe against this WSDL produces a few unexpected results. It generates a new class to represent the header, which is a fine approach, but the generated class looks somewhat suspect.
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.w3.org/2001/XMLSchema")]
[System.Xml.Serialization.XmlRootAttribute("ValidFrom", Namespace="http://test.sforce.com/", IsNullable=false)]
public class dateTime : System.Web.Services.Protocols.SoapHeader {
    
    /// <remarks/>
    [System.Xml.Serialization.XmlTextAttribute()]
    public string[] Text;
}
Huh, why is the value a string array and not a DateTime ?, this leads to a rather hokey client experience of
TestService ts = new TestService();
ts.ValidFrom = new dateTime();
ts.ValidFrom.Text = new String[1];
ts.ValidFrom.Text[0] = XmlConvert.ToString(DateTime.Now.AddSeconds(-1));
Console.WriteLine("{0}+{1}={2}", 1, 2, ts.add(1,2));
But, at least you can run it, and it does the right thing on the wire. On to the next example, test_2headers.wsdl, this adds yet another header of the same type, running wsdl.exe now gives us this, which makes even less sense than last time.
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.w3.org/2001/XMLSchema")]
[System.Xml.Serialization.XmlRootAttribute("ValidFrom", Namespace="http://test.sforce.com/", IsNullable=false)]
public class dateTime : System.Web.Services.Protocols.SoapHeader {
    
    /// <remarks/>
    [System.Xml.Serialization.XmlTextAttribute()]
    public string[] Text;
}

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(TypeName="dateTime", Namespace="http://www.w3.org/2001/XMLSchema")]
[System.Xml.Serialization.XmlRootAttribute("ValidUntil", Namespace="http://test.sforce.com/", IsNullable=false)]
public class dateTime1 : System.Web.Services.Protocols.SoapHeader {
    
    /// <remarks/>
    [System.Xml.Serialization.XmlTextAttribute()]
    public string[] Text;
}
Why does it need 2 different classes to represent the same type ?, note how one has a TypeName and one doesn't, also strange. But wait, it gets worse, this code compiles but doesn't run!, attempt to use the generated code and you'll be faced with
Unhandled Exception: System.InvalidOperationException: Method TestService.add can not be reflected. ---> System.InvalidOperationException: 
There was an error reflecting 'dateTime1'. ---> System.InvalidOperationException: There was an error reflecting type 'dateTime1'. ---> 
System.InvalidOperationException: Types dateTime1 and dateTime both use the XML type name, dateTime, from namespace
http://www.w3.org/2001/XMLSchema. Use XML attributes to specify a unique XML name and/or namespace for the type.
   at System.Xml.Serialization.XmlReflectionImporter.GetTypeMapping(String typeName, String ns, TypeDesc typeDesc)
   at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, Boolean repeats)
Grrhhh, back to the drawing board, I wonder if .NET 2.0 and Indigo repeat this braindeadedness?

Update: If you make the header element restrictions of the simple types (e.g. test_2headersB.wsdl), that'll fix the runtime reflection errors, but will still leave you with the dumbass string [] value instead of a DateTime.

<s:element name="ValidUntil">
 <s:simpleType>
      <s:restriction base="s:dateTime"/>
 </s:simpleType>
</s:element>
This finally explains why this is like it is.