I am using C # / WCF (.NET 3.5 SP1) and trying to access a SOAP service hosted through Apache / PHP. WSDL was first created, and then a service was created to match WSDL. I am trying to use svcutil to create WCF proxy classes to access this service.
I found an obvious code generation error in svcutil. This is because there are two separate SOAP operations that share a common input message (using different SOAP actions) and output two different types of array. When svcutil generates code, both operations are generated using the same request class and different response classes, and OperationContractAttribute is used to set the action for the operations. Unfortunately, MessageContractAttribute is set to the request class and seems to override this, with the result that both service calls contain the same SOAP action.
I was able to fix this problem by manually modifying the generated code to create a separate class for the second operation with another MessageContractAttribute and update all other links to use this class. But I'm really unhappy with this solution, and I'm looking for a way to either configure WSDL so that svcutil can interpret it better, or change the command line arguments to svcutil so that it can generate the correct code.
One more note: DataContractSerializer cannot import this WSDL, it must use XmlSerializer. Therefore, if someone can suggest a way to change the WSDL so that the DataContractSerializer can function, I would be happy to try this - I have full control over the WSDL.
I managed to create a VERY simple WSDL to illustrate this point:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.example.org/ArrayTest/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ArrayTest" targetNamespace="http://www.example.org/ArrayTest/"> <wsdl:types> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/ArrayTest/" > <xsd:complexType name="baseType1"> <xsd:sequence> <xsd:element name="string1" type="xsd:string" /> <xsd:element name="string2" type="xsd:string" /> </xsd:sequence> </xsd:complexType> <xsd:complexType name="listType1"> <xsd:sequence> <xsd:element name="baseType" type="tns:baseType1" maxOccurs="unbounded" /> </xsd:sequence> </xsd:complexType> <xsd:complexType name="baseType2"> <xsd:sequence> <xsd:element name="string1" type="xsd:string" /> <xsd:element name="string2" type="xsd:string" /> <xsd:element name="bool1" type="xsd:boolean" /> </xsd:sequence> </xsd:complexType> <xsd:complexType name="listType2"> <xsd:sequence> <xsd:element name="baseType" type="tns:baseType2" maxOccurs="unbounded" /> </xsd:sequence> </xsd:complexType> </xsd:schema> </wsdl:types> <wsdl:message name="NewOperationRequest"> <wsdl:part name="NewOperationRequest" type="xsd:string"/> </wsdl:message> <wsdl:message name="Array1OperationResponse"> <wsdl:part name="list1" type="tns:listType1"/> </wsdl:message> <wsdl:message name="Array2OperationResponse"> <wsdl:part name="list2" type="tns:listType2" /> </wsdl:message> <wsdl:portType name="ArrayTest"> <wsdl:operation name="Array1Operation"> <wsdl:input message="tns:NewOperationRequest"/> <wsdl:output message="tns:Array1OperationResponse"/> </wsdl:operation> <wsdl:operation name="Array2Operation"> <wsdl:input message="tns:NewOperationRequest"/> <wsdl:output message="tns:Array2OperationResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="ArrayTestSOAP" type="tns:ArrayTest"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="Array1Operation"> <soap:operation soapAction="http://www.example.org/ArrayTest/Array1Operation" /> <wsdl:input> <soap:body use="literal" namespace="http://www.example.org/ArrayTest/" /> </wsdl:input> <wsdl:output> <soap:body use="literal" namespace="http://www.example.org/ArrayTest/" /> </wsdl:output> </wsdl:operation> <wsdl:operation name="Array2Operation"> <soap:operation soapAction="http://www.example.org/ArrayTest/Array2Operation" /> <wsdl:input> <soap:body use="literal" namespace="http://www.example.org/ArrayTest/" /> </wsdl:input> <wsdl:output> <soap:body use="literal" namespace="http://www.example.org/ArrayTest/" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="ArrayTest"> <wsdl:port binding="tns:ArrayTestSOAP" name="ArrayTestSOAP"> <soap:address location="http://www.example.org/"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
Here is the command passed to svcutil:
svcutil /noconfig /o:arrayTest.cs ArrayTest.wsdl
Here is the code that was generated:
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // Runtime Version:2.0.50727.4959 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.ServiceContractAttribute(Namespace="http://www.example.org/ArrayTest/", ConfigurationName="ArrayTest")] public interface ArrayTest { // CODEGEN: Parameter 'list1' requires additional schema information that cannot be captured using the parameter mode. The specific attribute is 'System.Xml.Serialization.XmlArrayAttribute'. [System.ServiceModel.OperationContractAttribute(Action="http://www.example.org/ArrayTest/Array1Operation", ReplyAction="*")] [System.ServiceModel.XmlSerializerFormatAttribute()] [return: System.ServiceModel.MessageParameterAttribute(Name="list1")] Array1OperationResponse Array1Operation(Array1OperationRequest request); // CODEGEN: Parameter 'list2' requires additional schema information that cannot be captured using the parameter mode. The specific attribute is 'System.Xml.Serialization.XmlArrayAttribute'. [System.ServiceModel.OperationContractAttribute(Action="http://www.example.org/ArrayTest/Array2Operation", ReplyAction="*")] [System.ServiceModel.XmlSerializerFormatAttribute()] [return: System.ServiceModel.MessageParameterAttribute(Name="list2")] Array2OperationResponse Array2Operation(Array1OperationRequest request); } /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "3.0.4506.2152")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.example.org/ArrayTest/")] public partial class baseType1 { private string string1Field; private string string2Field; /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=0)] public string string1 { get { return this.string1Field; } set { this.string1Field = value; } } /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=1)] public string string2 { get { return this.string2Field; } set { this.string2Field = value; } } } /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "3.0.4506.2152")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.example.org/ArrayTest/")] public partial class baseType2 { private string string1Field; private string string2Field; private bool bool1Field; /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=0)] public string string1 { get { return this.string1Field; } set { this.string1Field = value; } } /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=1)] public string string2 { get { return this.string2Field; } set { this.string2Field = value; } } /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=2)] public bool bool1 { get { return this.bool1Field; } set { this.bool1Field = value; } } } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.MessageContractAttribute(WrapperName="Array1Operation", WrapperNamespace="http://www.example.org/ArrayTest/", IsWrapped=true)] public partial class Array1OperationRequest { [System.ServiceModel.MessageBodyMemberAttribute(Namespace="", Order=0)] public string NewOperationRequest; public Array1OperationRequest() { } public Array1OperationRequest(string NewOperationRequest) { this.NewOperationRequest = NewOperationRequest; } } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.MessageContractAttribute(WrapperName="Array1OperationResponse", WrapperNamespace="http://www.example.org/ArrayTest/", IsWrapped=true)] public partial class Array1OperationResponse { [System.ServiceModel.MessageBodyMemberAttribute(Namespace="", Order=0)] [System.Xml.Serialization.XmlArrayAttribute()] [System.Xml.Serialization.XmlArrayItemAttribute("baseType", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)] public baseType1[] list1; public Array1OperationResponse() { } public Array1OperationResponse(baseType1[] list1) { this.list1 = list1; } } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] [System.ServiceModel.MessageContractAttribute(WrapperName="Array2OperationResponse", WrapperNamespace="http://www.example.org/ArrayTest/", IsWrapped=true)] public partial class Array2OperationResponse { [System.ServiceModel.MessageBodyMemberAttribute(Namespace="", Order=0)] [System.Xml.Serialization.XmlArrayAttribute()] [System.Xml.Serialization.XmlArrayItemAttribute("baseType", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)] public baseType2[] list2; public Array2OperationResponse() { } public Array2OperationResponse(baseType2[] list2) { this.list2 = list2; } } [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] public interface ArrayTestChannel : ArrayTest, System.ServiceModel.IClientChannel { } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")] public partial class ArrayTestClient : System.ServiceModel.ClientBase<ArrayTest>, ArrayTest { public ArrayTestClient() { } public ArrayTestClient(string endpointConfigurationName) : base(endpointConfigurationName) { } public ArrayTestClient(string endpointConfigurationName, string remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public ArrayTestClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress) { } public ArrayTestClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] Array1OperationResponse ArrayTest.Array1Operation(Array1OperationRequest request) { return base.Channel.Array1Operation(request); } public baseType1[] Array1Operation(string NewOperationRequest) { Array1OperationRequest inValue = new Array1OperationRequest(); inValue.NewOperationRequest = NewOperationRequest; Array1OperationResponse retVal = ((ArrayTest)(this)).Array1Operation(inValue); return retVal.list1; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] Array2OperationResponse ArrayTest.Array2Operation(Array1OperationRequest request) { return base.Channel.Array2Operation(request); } public baseType2[] Array2Operation(string NewOperationRequest) { Array1OperationRequest inValue = new Array1OperationRequest(); inValue.NewOperationRequest = NewOperationRequest; Array2OperationResponse retVal = ((ArrayTest)(this)).Array2Operation(inValue); return retVal.list2; } }