We’ve been slowly migrating our services from asmx to WCF, but as we’re still using BizTalk 2006 with no support for WCF we’ve been exposing endpoints configured for basicHttpBinding and consume them using the SOAP adapter.
Generally speaking things have been going well, although we completely gave up on the idea of moving the services to WCF and NOT have to change the client, until yesterday we’ve stumbled into a serialisation issue –
The SOAP adapter, as part of its work deserialises the request message arriving through the send port into t he web service proxy class it generated, before calling the web service (which would result in the class now being serialised back into xml, which is another story); that deserialisation failed.
The error message was clear enough and indicated it failed to deserialise an enum parameter the service was expecting, and that ran a bell – I posted on exactly that back in September, but after carefully checking and re-checking everything we could swear that our message (which was now suspended) matches perfectly the schema generated by the add web reference wizard; what’s going on then??
After chasing our tail for a short while we brought up reflector to the rescue and found out the cause of our woe is a combination of a difference in behaviour between WCF and ASMX and the use of BizTalk – here are the details –
Consider the following asmx web method –
[WebMethod] public string GetDataUsingDataContract(CompositeType.someEnum myEnum) { return "Hello World"; }
With CompositeType being
public class CompositeType { public enum someEnum { Value1, Value2 } }
(..and pretend CompositeType has many more things, but these are irrelevant to this topic)
The definition for myEnum in the WSDL looks like
<s:element minOccurs="1" maxOccurs="1" name="myEnum" type="tns:someEnum" />
Where the type tns:someEnum looks like
<s:simpleType name="someEnum"> <s:restriction base="s:string"> <s:enumeration value="Value1" /> <s:enumeration value="Value2" /> </s:restriction> </s:simpleType>
As a result the definition of the enum in a proxy generated via the add web reference VS 2005 option (which is what BizTalk would use) looks like –
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.3053")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")] public enum someEnum
{
Value1,
Value2,
}
All makes sense.
Now, let’s look at what WCF does in the same case; consider the following service –
[ServiceContract] public interface IService1 { [OperationContract] string GetDataUsingDataContract(CompositeType.someEnum myEnum); } [DataContract] public class CompositeType { public enum someEnum { Value1, Value2 } }
The WSDL generated looks like
<xs:simpleType name="CompositeType.someEnum"> <xs:restriction base="xs:string"> <xs:enumeration value="Value1" /> <xs:enumeration value="Value2" /> </xs:restriction> </xs:simpleType> <xs:element name="CompositeType.someEnum" nillable="true" type="tns:CompositeType.someEnum" />
The key difference is that the name of the class containing the enum has made it into the type name for the enum, which never happened in the ASMX version.
As a result the proxy is generated as such –
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")] [System.Runtime.Serialization.DataContractAttribute(Name="CompositeType.someEnum",
Namespace="http://schemas.datacontract.org/2004/07/WcfService1")] public enum CompositeTypesomeEnum : int
{ [System.Runtime.Serialization.EnumMemberAttribute()] Value1 = 0, [System.Runtime.Serialization.EnumMemberAttribute()] Value2 = 1, }
Again – note the name given to the element now contains the class name and, crucially, a dot (’.’).
On it’s own – nothing to malicious – although it’s another nail in the coffin for the idea that you can substitute web service with WCF service, configured them to use basicHttpBinding and all should be the same (ok – am I the only one still wishing this was possible?)
Enters BizTalk.
When you use the add web reference wizard to add a reference to the WCF service, BizTalk generates all the schemas and proxy for you, which is what you would use to create requests going to the service (and process responses).
Because the WSDL of the WCF service contains the longer name of the enum (with the class name, the dot and the enum name) the .net proxy generated is identical to the one created for the WCF service above; the schema, however, is generated incorrectly!
BizTalk “kindly” decides that having dots in the element name is not a good idea and removes it so the schema generated looks like this –
<xs:schema xmlns:tns="http://schemas.datacontract.org/2004/07/WcfService1" elementFormDefault="qualified"
targetNamespace="http://schemas.datacontract.org/2004/07/WcfService1"
xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="CompositeTypesomeEnum" type="tns:CompositeTypesomeEnum" /> <xs:simpleType name="CompositeTypesomeEnum"> <xs:restriction base="xs:string"> <xs:enumeration value="Value1" /> <xs:enumeration value="Value2" /> </xs:restriction> </xs:simpleType> </xs:schema>
“CompositeTypesomeEnum”??????
Well, we’ve seen this, and created a message with exactly that element, which – of course – the SOAP adapter failed to deserialise into
[System.Runtime.Serialization.DataContractAttribute(Name="CompositeType.someEnum",
Namespace="http://schemas.datacontract.org/2004/07/WcfService1")] public enum CompositeTypesomeEnum : int
{ [System.Runtime.Serialization.EnumMemberAttribute()] Value1 = 0, [System.Runtime.Serialization.EnumMemberAttribute()] Value2 = 1, }
The solution was fairly simple – we’ve simple change our xsl to put the element name as the .net proxy requires it, and not as the schema describes it, and it all worked well.