Why is my WCF web service presenting this object i

2019-01-09 13:56发布

The Context: I'm trying to integrate with DocuSign's Connect notification service. I've set up a WCF service with a method called DocuSignConnectUpdate which takes a DocuSignEnvelopeInformation as its only parameter, as specified by DocuSign. This DocuSignEnvelopeInformation object comes from a reference to their API, so that they can pass this object to my web service, and I know exactly what to expect. DocuSign asks for my service address and the namespace, which I have configured on their site.

The Problem: The XML that DocuSign sends is what I would expect. The DocuSignEnvelopeInformation and its children are in the namespace "http://www.docusign.net/API/3.0" and the element names match the object names:

<DocuSignEnvelopeInformation xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.docusign.net/API/3.0">
    <EnvelopeStatus>...</EnvelopeStatus>
</DocuSignEnvelopeInformation>

But my web service is expecting something different, in the wrong namespace, and with modified element names. This is how the DocuSignConnectUpdate method is defined in my WSDL:

<xs:element name="DocuSignConnectUpdate">
    <xs:complexType>
        <xs:sequence>
            <xs:element minOccurs="0" name="DocuSignEnvelopeInformation" nillable="true" type="tns:DocuSignEnvelopeInformation"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

And this is how this is how the DocuSignEnvelopeInformation type is defined in my WSDL:

<xs:complexType name="DocuSignEnvelopeInformation">
    <xs:sequence>
        <xs:element xmlns:q1="http://schemas.datacontract.org/2004/07/System.ComponentModel" name="PropertyChanged" nillable="true" type="q1:PropertyChangedEventHandler"/>
        <xs:element name="documentPDFsField" nillable="true" type="tns:ArrayOfDocumentPDF"/>
        <xs:element name="envelopeStatusField" nillable="true" type="tns:EnvelopeStatus"/>
        <xs:element name="timeZoneField" nillable="true" type="xs:string"/>
        <xs:element name="timeZoneOffsetField" type="xs:int"/>
        <xs:element name="timeZoneOffsetFieldSpecified" type="xs:boolean"/>
    </xs:sequence>
</xs:complexType>

Element names like envelopeStatusField are the names of the private variables used in the auto-generated code. The public property names match the xml that DocuSign sends. The auto-generated code also tags each object with the correct docusign namespace using XmlTypeAttribute. So from looking at the auto-generated code, I would expect my service to be happy with the input, but the WSDL generated is different, as shown above, and my service fails to deserialize the xml.

Some Code: The auto-generated declaration of DocuSignEnvelopeInformation:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.17929")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.docusign.net/API/3.0")]
public partial class DocuSignEnvelopeInformation : object, System.ComponentModel.INotifyPropertyChanged {

    private EnvelopeStatus envelopeStatusField;

    private DocumentPDF[] documentPDFsField;

    private string timeZoneField;

    private int timeZoneOffsetField;

    private bool timeZoneOffsetFieldSpecified;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Order=0)]
    public EnvelopeStatus EnvelopeStatus {
    ...
    ...

The OperationContract for the only method:

[SoapHeaders]
[ServiceContract(Namespace = "http:/MyNameSpace")]
public interface IDocusignEventListener
{
    [OperationContract]
    [FaultContract(typeof(ErrorMessageCollection), Action = Constants.FaultAction)]
    string DocuSignConnectUpdate(DocuSignEnvelopeInformation DocuSignEnvelopeInformation);
}

The method that DocuSign calls

[ServiceBehavior(Namespace = "http:/MyNameSpace", ConfigurationName = "DocusignEventListener")]
public class DocusignEventListener : IDocusignEventListener
{
    public string DocuSignConnectUpdate(DocuSignEnvelopeInformation DocuSignEnvelopeInformation)
   {
       ...

       return DocuSignEnvelopeInformation.EnvelopeStatus.EnvelopeID;
    }
}

So, again, the question is why is the wsdl showing up this way? Why is the object different from the reference I pulled it from? And more importantly, can I fix it?

1条回答
ら.Afraid
2楼-- · 2019-01-09 14:39

It is stunning how many hours I've spent on this, how many solutions I've tried, how many links I've followed, and how many SO answers I've read that did not answer my question before finally finding that the answer was sitting right here on SO for more than 2 years!

The root problem is that the DocuSignEnvelopeInformation object is being serialized and deserialized by DataContractSerializer by default. This essentially serializes the private member variables that make up the object in their local namespace instead of the public properties. Infuriatingly, this is the default serializer for WCF services. If the auto-generated code of the service application at least marked the sample methods with [DataContractFormat] explicitly, we would have a clue to follow, but it is just an invisible default that you have to divine somehow.

The solution is to mark each method with [XmlSerializerFormat] in the interface. This replaces DataContractSerializer with XmlSerializer as the serializer for the method parameters:

[SoapHeaders]
[ServiceContract(Namespace = "http://www.docusign.net/API/3.0")]
public interface IDocusignEventListener
{
    [OperationContract]
    [XmlSerializerFormat]
    [FaultContract(typeof(ErrorMessageCollection), Action = Constants.FaultAction)]
    string DocuSignConnectUpdate(DocuSignEnvelopeInformation DocuSignEnvelopeInformation);
}

And just like that, the public properties, with their declared namespaces and everything I need, are now serialized instead of the private data!

For my specific concern, to receive calls from DocuSign's Connect notification service, I still had one small namespace problem. The root level parameter, the DocuSignEnvelopeInformation, was still in the namespace of the method call. I’m not sure why. For now, I'm just putting the method call itself in the DocuSign API namespace (as you can see in the code sample above). The service now deserializes these calls correctly.

查看更多
登录 后发表回答