Wednesday, January 2, 2008

BTS 2006 Soap Adapter XmlSerializer Bug ?

In one of the projects my team recently worked on, we notice a mysteries behavior of the Soap Adapter. We had to consume a web service that contains some value-type elements.

When I looked into the WSDL file, I've notice that each element contains two attributes:

  • minOccurs="0" - you can simply drop the element from your input
  • nill="True" - you can pass null value for this element.

Now, in general, the combination of xsi:nill=True" and minOccurs="0" doesn't make match sense because you don't need to pass null value if you can just drop the whole element - But, this was the case.

I added the Web reference and happily deployed my project.

The service refused to work. so, after investigation and some little sniffing (I just LOVE WireShark...) I realized that all of the nullable value-typed elements are missing.

It's odd because when I saved the tracking messages directly from the HAT, all of them appeared in the output xml.

BizTalk is saying 'A' but WireShark was showing 'B' - where was the problem?

To figure it, Lets consume some web service from a simple C# console application. The WSDL contains a complex type with the name "MyClass". the class has two value-type member. one of them (SomeNullableInt) is configured as nillable="true"

<s:complexType name="MyClass">
  <s:sequence>
    <s:element minOccurs="0" name="SomeInt" type="s:int" />
    <s:element minOccurs="0" name="SomeNullableInt"nillable="true" type="s:int"/>
  </s:sequence>
 </s:complexType>
 
Now, add the web reference to your project and see that in the auto-generated proxy class, the "SomeNullableInt" member declared as Nullable<T> type:

[Serializable()]
public partial class MyClass
{
       public int SomeInt {...}

       [XmlElement(IsNullable=true)]
       public System.Nullable<int> SomeNullableInt {...}
}
 
Next, create an instance of MyClass object and init the members with values. then serialize the object into XmlDocument and write the OuterXml to the Console window.

static void Main(string[] args)
{
    MyClass myClass = new MyClass();
    myClass.SomeInt = 1234;
    myClass.SomeNullableInt = 1234;

    myClass.SomeIntSpecified = true;
 
    XmlDocument doc = new XmlDocument();
     MemoryStream ms = new MemoryStream();
     XmlSerializer s = new XmlSerializer(typeof(MyClass));

     s.Serialize(ms, myClass);
     ms.Seek(0, SeekOrigin.Begin);
    doc.Load(ms);

    Console.WriteLine(doc.OuterXml);
    Console.ReadLine();
}

So, As you can see, the Nullable<T> member is ignored in the serializing process.It's look like the default .Net System.Xml.Serialization.XmlSerializer can't serialize Nullable<T> Type.

The actual reason to this behavior is because of the minOccurs="0" attribute. this means that the element doesn't have to occur in the xml document, the XmlSerialization mechanism checking the element using the '<elementName>Specified' Property generated with the proxy class. it's seems like the proxy generator doesn't checking this property for Nullable<T> types, maybe becouase you would expect the XmlSerialization engine to be smart enough to use the 'HasValue' property of the Nullable<T> class to get this information.

So let's change the Wsdl to be like this:

<s:complexType name="MyClass">
<s:sequence>

    <s:element minOccurs="0" name="SomeInt" type="s:long" />
    <s:element name="SomeNullableInt" nillable="true" type="s:long" />
  </s:sequence>
</s:complexType>


It's little known that the SOAP Adapter is NOT sending the xml in the form and shape it received from the messaging engine. the actual processing in the method SetMethodAndParameters(MethodInfo method) under Microsoft.BizTalk.Soap.SoapMessage class includes deserializing the message parts into the required web method parameters as CLR objects. in this way, the SOAP adapter can find the matching operation even if there's more then one operation with the same name (don't forget that the SOAP Adapter is Reflection based). the final step is the Send() method that Invokes the proxy class (again, using Reflection).

The adapter is deserializing the xml into CLR objects using XmlSerializer.

So, what was really happening is that BizTalk was sending the message to the Soap Adapter. the adapter deserialized the object using the default XmlSerializer class and therefore all the Nullable<T> elements are ignored, at the end of the sending process the adapter serialized it again in the Invoke method of the proxy class and send the new xml to the web service.

Conclusion: To enable BizTalk Soap Adpater to serialize nullable value-type elements you need to remove the minOccurs="0" from all of the elements that contains nillable="true" attribute.

So, you have tow options:

  • Change the web-service itself.
  • Change only the wsdl file and then add the web reference from the fixed wsdl file.

If you choose the second option - just keep in mind that after every change or update of the web service, you need to get the new version of the wsdl, fix it by hand, and then - add the web referance from the fixed version, again.

Another thing to keep in mind is that you can't really be sure about the message BizTalk submitted through the Adapter Framework because the saving tracked massages from the orchestration using HAT is showing only the state in the Messaging engine before passing the message to the adapter for process and submit.

 
Copyright © 2007 | Diseñado por Blog and Web