I am using C# / WCF (.NET 3.5 SP1) and trying to access a SOAP service hosted through Apache / PHP. The WSDL was generated first and then the service was built to conform to the WSDL. I am trying to use svcutil to generate WCF proxy classes to access this service.
I have discovered an apparent code generation error in svcutil. It seems to occur because there are two separate SOAP operations that share a common input message (using different SOAP Actions) and output two different array types. When svcutil generates code, both operations are generated using the same request class and different response classes, and the OperationContractAttribute is used to set the Action for the operations. Unfortunately, a MessageContractAttribute is being set on the request class and it seems to override this, causing both service calls to contain the same SOAP action.
I was able to correct this issue by hand-modifying the generated code to create a separate class for the second operation with a different MessageContractAttribute and update all the other references to use this class. But I'm really not happy with this solution and I am looking for a way to either tweak the WSDL so that svcutil can interpret it better, or change the command-line arguments to svcutil so it can generate the correct code.
One other note, DataContractSerializer cannot import this WSDL, it must use the XmlSerializer. So if someone can suggest a way to modify the WSDL such that DataContractSerializer can function, I would be happy to try that - I have full control over the WSDL.
I was able 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 in开发者_StackOverflow中文版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;
}
}
The only answer I have come up with so far is to create an additional message type with identical parts, i.e.:
<wsdl:message name="Array2OperationRequest">
<wsdl:part name="NewOperationRequest" type="xsd:string"/>
</wsdl:message>
I really don't like this solution since I have created a redundant message with the exact same structure. It seems that the svcutil should be able to generate different SOAP Actions for the same message type. And, in fact, if the response is not an array, it does this just fine (I have tested with oeprations that return, say, a bool, and they can share the same request message and the code generates correctly).
精彩评论