Contract Generation from WSDL/XML Schema - DataContractSerializer vs. XmlSerializer
WCF LOB Adapter SDK metadata object model generates WSDL / XML Schema with constructs that are well-liked by DataContractSerializer. The Svcutil.Exe creates a proxy / service interface from this WSDL containing attributes such as ServiceContract, OperationContract, DataContract, DataMember, etc. If the adapter developer provides own XML Schema structure in OperationMetadata and/or TypeMetadata derived class and the XML Schema contains constructs that are ignored or forbidden by DataContractSerializer, the Svcutil.Exe and Add Adapter Service Reference Visual Studio Plug-In falls back to use XmlSerializer for serialization.
Following are some examples of forbidden XML Schema constructs:
· Xsd:choice
· Xsd:group
· Xsd:any
· Xsd:ref
· Xsd:attribute
The metadata object model in WCF LOB Adapter SDK avoids generating any XML Schema constructs that are ignored and forbidden by DataContractSerializer. But if the adapter developer wants to provide their own schema, then it is outside the Adapter SDK control and the WSDL will contain the XML Schema definitions as provided by the adapter developer. The generate CLR proxy will have the XmlSerializer attributes defined on operations and its members.
What does this mean to the end-user?
Adapter Consumers who want to use the adapter in BizTalk Server are not affected with XML/CLR (de) serialization, as they are working directly with XML Schema types. As long as the XML message conforms to the XML Schema, the adapter understands it.
But if the same adapter is also now being used within a .NET application that will convert the WSDL / XML Schema into relevant CLR objects, the end-users who are working with the generated CLR object may contain definitions that are “bizarre”.
Let me give you an example. Consider an XML Schema with optional elements defined (i.e. minOccurs=’0’). When using DataContractSerializer, the minOccurs should map to IsRequired attribute in the CLR object. The XmlSerializer on the other hand generates a proxy that contains the xxxSpecified properties for each optional element. The xxxSpecified was a pain for our customers in .NET 2.0 when used with xsd.exe or wsdl.exe. Now, let’s add another complex type in the WSDL that uses a forbidden construct such as “xsd:attribute”. The Svcutil.Exe will revert to use XmlSerializer and generate an output that contains xxxSpecified fields.
See the sample code that illustrates the above point.
Sample WSDL (without any forbidden construct)
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:tns="hello://Microsoft.WCF.Samples.Adapters" xmlns:doc="https://schemas.microsoft.com/servicemodel/adapters/metadata/documentation" xmlns:wsaw="https://www.w3.org/2006/05/addressing/wsdl" xmlns:ns2="hello://Microsoft.WCF.Samples.Adapters" targetNamespace="hello://Microsoft.WCF.Samples.Adapters" xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/">
<wsdl:types>
<schema elementFormDefault="qualified" targetNamespace="hello://Microsoft.WCF.Samples.Adapters" version="1.0" xmlns="https://www.w3.org/2001/XMLSchema">
<complexType name="MyType">
<sequence>
<element minOccurs="0" maxOccurs="1" nillable="true" type="double" name="ONillableDoubleField" />
<element minOccurs="1" maxOccurs="1" nillable="true" type="double" name="MNillableDoubleField" />
<element minOccurs="1" maxOccurs="1" type="double" name="MandatoryDoubleField" />
<element minOccurs="0" maxOccurs="1" type="double" name="OptDoubleField" />
</sequence>
</complexType>
<element name="SayHelloWorld">
<annotation>
<documentation>
<doc:action>Hello/SayHelloWorld</doc:action>
</documentation>
</annotation>
<complexType>
<sequence>
<element minOccurs="1" maxOccurs="1" name="inName" nillable="true" type="string">
<annotation>
<documentation>Hello World is said by this name.</documentation>
</annotation>
</element>
<element minOccurs="0" maxOccurs="1" name="OptionalElement" type="dateTime" />
<element minOccurs="0" maxOccurs="1" name="Container" type="ns2:MyType"/>
</sequence>
</complexType>
</element>
<element name="SayHelloWorldResponse">
<annotation>
<documentation>
<doc:action>Hello/SayHelloWorld/response</doc:action>
</documentation>
</annotation>
<complexType>
<sequence>
<element minOccurs="1" maxOccurs="1" name="SayHelloWorldResult" nillable="true" type="string" />
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>
<wsdl:message name="HelloWorld_SayHelloWorld_InputMessage">
<wsdl:part name="parameters" element="ns2:SayHelloWorld" />
</wsdl:message>
<wsdl:message name="HelloWorld_SayHelloWorld_OutputMessage">
<wsdl:part name="parameters" element="ns2:SayHelloWorldResponse" />
</wsdl:message>
<wsdl:portType name="HelloWorld">
<wsdl:operation name="SayHelloWorld">
<wsdl:input wsaw:Action="Hello/SayHelloWorld" message="ns2:HelloWorld_SayHelloWorld_InputMessage" />
<wsdl:output wsaw:Action="Hello/SayHelloWorld/response" message="ns2:HelloWorld_SayHelloWorld_OutputMessage" />
</wsdl:operation>
</wsdl:portType>
</wsdl:definitions>
SVCUTIL generated service interface proxy
svcutil HelloWorld.wsdl /out:HelloWorld.DataContractSereializer.cs
[assembly: System.Runtime.Serialization.ContractNamespaceAttribute("hello://Microsoft.WCF.Samples.Adapters", ClrNamespace="microsoft.wcf.samples.adapters")]
namespace microsoft.wcf.samples.adapters
{
using System.Runtime.Serialization;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute()]
public partial class MyType : object, System.Runtime.Serialization.IExtensibleDataObject
{
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
private System.Nullable<double> ONillableDoubleFieldField;
private System.Nullable<double> MNillableDoubleFieldField;
private double MandatoryDoubleFieldField;
private double OptDoubleFieldField;
public System.Runtime.Serialization.ExtensionDataObject ExtensionData
{
get
{
return this.extensionDataField;
}
set
{
this.extensionDataField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public System.Nullable<double> ONillableDoubleField
{
get
{
return this.ONillableDoubleFieldField;
}
set
{
this.ONillableDoubleFieldField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, Order=1)]
public System.Nullable<double> MNillableDoubleField
{
get
{
return this.MNillableDoubleFieldField;
}
set
{
this.MNillableDoubleFieldField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, Order=2)]
public double MandatoryDoubleField
{
get
{
return this.MandatoryDoubleFieldField;
}
set
{
this.MandatoryDoubleFieldField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute(Order=3)]
public double OptDoubleField
{
get
{
return this.OptDoubleFieldField;
}
set
{
this.OptDoubleFieldField = value;
}
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters", ConfigurationName="HelloWorld")]
public interface HelloWorld
{
[System.ServiceModel.OperationContractAttribute(Action="Hello/SayHelloWorld", ReplyAction="Hello/SayHelloWorld/response")]
string SayHelloWorld(string inName, System.DateTime OptionalElement, microsoft.wcf.samples.adapters.MyType Container);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface HelloWorldChannel : HelloWorld, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class HelloWorldClient : System.ServiceModel.ClientBase<HelloWorld>, HelloWorld
{
public HelloWorldClient()
{
}
public HelloWorldClient(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public HelloWorldClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public HelloWorldClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public HelloWorldClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public string SayHelloWorld(string inName, System.DateTime OptionalElement, microsoft.wcf.samples.adapters.MyType Container)
{
return base.Channel.SayHelloWorld(inName, OptionalElement, Container);
}
}
Sample WSDL (with a forbidden construct)
Change the WSDL to include a DataContractSerializer forbidden construct within the complex type MyType. Keep everything else as is.
<complexType name="MyType">
<sequence>
<element minOccurs="0" maxOccurs="1" nillable="true" type="double" name="ONillableDoubleField" />
<element minOccurs="1" maxOccurs="1" nillable="true" type="double" name="MNillableDoubleField" />
<element minOccurs="1" maxOccurs="1" type="double" name="MandatoryDoubleField" />
<element minOccurs="0" maxOccurs="1" type="double" name="OptDoubleField" />
</sequence>
<attribute default="X" name="xAttribute" type="string" />
</complexType>
svcutil HelloWorld-xmltypes.wsdl /out:HelloWorld.XmlSerializer.cs
Check the impact of doing this on the generated proxy and notice other attributes such as –
· XmlSerializerFormatAttribute
· XmlTypeAttribute
· XmlElementAttribute
· XmlAttributeAttribute
· MessageContractAttribute
· MessageBodyMemberAttribute
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters", ConfigurationName="HelloWorld")]
public interface HelloWorld
{
// CODEGEN: Parameter 'SayHelloWorldResult' requires additional schema information that cannot be captured using the parameter mode. The specific attribute is 'System.Xml.Serialization.XmlElementAttribute'.
[System.ServiceModel.OperationContractAttribute(Action="Hello/SayHelloWorld", ReplyAction="Hello/SayHelloWorld/response")]
[System.ServiceModel.XmlSerializerFormatAttribute()]
SayHelloWorldResponse SayHelloWorld(SayHelloWorldRequest request);
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "3.0.4506.30")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters")]
public partial class MyType
{
private System.Nullable<double> oNillableDoubleFieldField;
private bool oNillableDoubleFieldFieldSpecified;
private System.Nullable<double> mNillableDoubleFieldField;
private double mandatoryDoubleFieldField;
private double optDoubleFieldField;
private bool optDoubleFieldFieldSpecified;
private string xAttributeField;
public MyType()
{
this.xAttributeField = "X";
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=0)]
public System.Nullable<double> ONillableDoubleField
{
get
{
return this.oNillableDoubleFieldField;
}
set
{
this.oNillableDoubleFieldField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool ONillableDoubleFieldSpecified
{
get
{
return this.oNillableDoubleFieldFieldSpecified;
}
set
{
this.oNillableDoubleFieldFieldSpecified = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Order=1)]
public System.Nullable<double> MNillableDoubleField
{
get
{
return this.mNillableDoubleFieldField;
}
set
{
this.mNillableDoubleFieldField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=2)]
public double MandatoryDoubleField
{
get
{
return this.mandatoryDoubleFieldField;
}
set
{
this.mandatoryDoubleFieldField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=3)]
public double OptDoubleField
{
get
{
return this.optDoubleFieldField;
}
set
{
this.optDoubleFieldField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool OptDoubleFieldSpecified
{
get
{
return this.optDoubleFieldFieldSpecified;
}
set
{
this.optDoubleFieldFieldSpecified = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
[System.ComponentModel.DefaultValueAttribute("X")]
public string xAttribute
{
get
{
return this.xAttributeField;
}
set
{
this.xAttributeField = value;
}
}
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.MessageContractAttribute(WrapperName="SayHelloWorld", WrapperNamespace="hello://Microsoft.WCF.Samples.Adapters", IsWrapped=true)]
public partial class SayHelloWorldRequest
{
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters", Order=0)]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string inName;
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters", Order=1)]
public System.DateTime OptionalElement;
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters", Order=2)]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public MyType Container;
public SayHelloWorldRequest()
{
}
public SayHelloWorldRequest(string inName, System.DateTime OptionalElement, MyType Container)
{
this.inName = inName;
this.OptionalElement = OptionalElement;
this.Container = Container;
}
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.MessageContractAttribute(WrapperName="SayHelloWorldResponse", WrapperNamespace="hello://Microsoft.WCF.Samples.Adapters", IsWrapped=true)]
public partial class SayHelloWorldResponse
{
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="hello://Microsoft.WCF.Samples.Adapters", Order=0)]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string SayHelloWorldResult;
public SayHelloWorldResponse()
{
}
public SayHelloWorldResponse(string SayHelloWorldResult)
{
this.SayHelloWorldResult = SayHelloWorldResult;
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface HelloWorldChannel : HelloWorld, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class HelloWorldClient : System.ServiceModel.ClientBase<HelloWorld>, HelloWorld
{
public HelloWorldClient()
{
}
public HelloWorldClient(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public HelloWorldClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public HelloWorldClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public HelloWorldClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
SayHelloWorldResponse HelloWorld.SayHelloWorld(SayHelloWorldRequest request)
{
return base.Channel.SayHelloWorld(request);
}
public string SayHelloWorld(string inName, System.DateTime OptionalElement, MyType Container)
{
SayHelloWorldRequest inValue = new SayHelloWorldRequest();
inValue.inName = inName;
inValue.OptionalElement = OptionalElement;
inValue.Container = Container;
SayHelloWorldResponse retVal = ((HelloWorld)(this)).SayHelloWorld(inValue);
return retVal.SayHelloWorldResult;
}
}
Summary: Avoid, where possible, having constructs in the WSDL / XML Schema that can create a less optimum proxy for the end-user (applicable when using a top-down approach with creating a service interface from WSDL / XML Schema).