Search code examples
c#wcfsoap.net-corewsdl

dotnet-svcutil-generated WCF proxy (and even a custom Channel<>) are returning null for a valid SOAP response


Proxy generated with dotnet-svcutil 2.0.1 (dotnet-svcutil --sync --outputDir . http://XXX/?WSDL) and System.ServiceModel.* 4.7.0, calling code and?WSDL below. The proxy simply fails to deserialize the valid response and just returns null. Have tried both .NET Core 3.0 and 3.1 on Windows 10 and macOS Catalina, same null result. Fiddler request and response attached along with?WSDL from the server (server beyond my control).

With the proxy I am using @shmao's set_mode workaround (https://github.com/dotnet/wcf/issues/2219) to avoid the 'JScript/CSharp scripts is not supported' exception. Further, I have to remove the Namespace="" attributes to get even the request part working. I have added EventListeners and dumped everything at Verbose from all event sources, no warnings/errors, just the null.

I have also tried the Channel-based MessageContract/DataContract approach, ultimately same null result (!), I am unable to leverage any .NET Core WCF-based code to deserialize the given response.

Any solution or even partial solution to deserializing given response using .NET Core 3.1 WCF will be considered, ideally with dotnet-svcutil. Uncovering a warning/error or even getting access to the response string manually would still be an improvement over a non-WCF string/HttpRequest-based approach.

      WSWebServiceSoapPortClient proxy;
      try {
        proxy = new WSWebServiceSoapPortClient(new BasicHttpBinding(),
          new EndpointAddress("http://XXX"));

        await proxy.OpenAsync();
      } catch (Exception e) {
        Console.WriteLine(e.Message);
        return;
      }

      if (proxy.State == System.ServiceModel.CommunicationState.Faulted) {
        System.Console.WriteLine("Unable to connect to the proxy.");
        return;
      }

      var one = new WSUserLoginRequest1(new WSUserLoginRequest() {
        userName = "XXX",
        userPassword = "XXX",
      });
      WSUserLoginResponse1 wsUserLoginResponse = null;

      try {
        wsUserLoginResponse = await proxy.WSUserLoginAsync(one);   // returns null
      } catch (Exception e) {
        Console.WriteLine(e.ToString());
        return;
      }

Relevant WSDL from server

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" 
  xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" 
  xmlns:s="http://www.w3.org/2001/XMLSchema" 
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
  xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" 
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" 
  xmlns:tns="WSWebService" name="WSWebService" targetNamespace="WSWebService">
  <wsdl:types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="WSWebService">

      ...

      <s:complexType name="WSUserLoginRequest">
        <s:sequence>
          <s:element name="userName" type="s:string" />
          <s:element name="userPassword" type="s:string" />
        </s:sequence>
      </s:complexType>
      <s:complexType name="WSUserLoginResponse">
        <s:sequence>
          <s:element name="userToken" type="s:string" />
          <s:element name="wsdlVersion" type="s:string" minOccurs="1" maxOccurs="1" default="2.0.0.0" />
          <s:element name="result" type="s:int" />
          <s:element name="resultString" type="s:string" />
        </s:sequence>
      </s:complexType>

      ...

      <wsdl:message name="WSUserLoginSoapIn">
        <wsdl:part name="parameters" type="tns:WSUserLoginRequest" />
      </wsdl:message>
      <wsdl:message name="WSUserLoginSoapOut">
        <wsdl:part name="parameters" type="tns:WSUserLoginResponse" />
      </wsdl:message>

      ...

      <wsdl:operation name="WSUserLogin">
        <wsdl:documentation>Authenticate user using provided username and password.</wsdl:documentation>
        <wsdl:input message="tns:WSUserLoginSoapIn" />
        <wsdl:output message="tns:WSUserLoginSoapOut" />
      </wsdl:operation>

request response

(EDIT)

The class definition for WSUserLoginResponse1 as generated by dotnet-svcutil 2.0.1:

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.ServiceModel.MessageContractAttribute(WrapperName = "WSUserLoginResponse", WrapperNamespace = "WSWebService", IsWrapped = true)]
public partial class WSUserLoginResponse1 {

  [System.ServiceModel.MessageBodyMemberAttribute(Namespace = "", Order = 0)]
  public WSUserLoginResponse parameters;

  public WSUserLoginResponse1() {
  }

  public WSUserLoginResponse1(WSUserLoginResponse parameters) {
    this.parameters = parameters;
  }
}

(EDIT 2) WSUserLoginResponse from dotnet-svcutil as suggested.

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "WSWebService")]
public partial class WSUserLoginResponse {

  private string userTokenField;

  private string wsdlVersionField;

  private int resultField;

  private string resultStringField;

  public WSUserLoginResponse() {
    this.wsdlVersionField = "2.0.0.0";
  }

  /// <remarks/>
  [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 0)]
  public string userToken {
    get {
      return this.userTokenField;
    }
    set {
      this.userTokenField = value;
    }
  }

  /// <remarks/>
  [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 1)]
  public string wsdlVersion {
    get {
      return this.wsdlVersionField;
    }
    set {
      this.wsdlVersionField = value;
    }
  }

  /// <remarks/>
  [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 2)]
  public int result {
    get {
      return this.resultField;
    }
    set {
      this.resultField = value;
    }
  }

  /// <remarks/>
  [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 3)]
  public string resultString {
    get {
      return this.resultStringField;
    }
    set {
      this.resultStringField = value;
    }
  }
}

(EDIT 3) WSUserLoginResponse from wsdl.exe as suggested. There is no WSUserLoginResponse1

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1087.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="WSWebService")]
    public partial class WSUserLoginResponse : object, System.ComponentModel.INotifyPropertyChanged {

        private string userTokenField;

        private string wsdlVersionField;

        private int resultField;

        private string resultStringField;

        public WSUserLoginResponse() {
            this.wsdlVersionField = "2.0.0.0";
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=0)]
        public string userToken {
            get {
                return this.userTokenField;
            }
            set {
                this.userTokenField = value;
                this.RaisePropertyChanged("userToken");
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=1)]
        public string wsdlVersion {
            get {
                return this.wsdlVersionField;
            }
            set {
                this.wsdlVersionField = value;
                this.RaisePropertyChanged("wsdlVersion");
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=2)]
        public int result {
            get {
                return this.resultField;
            }
            set {
                this.resultField = value;
                this.RaisePropertyChanged("result");
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=3)]
        public string resultString {
            get {
                return this.resultStringField;
            }
            set {
                this.resultStringField = value;
                this.RaisePropertyChanged("resultString");
            }
        }

        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName) {
            System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
            if ((propertyChanged != null)) {
                propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
    }

(EDIT 4) Standalone wsdl.

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" 
  xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" 
  xmlns:s="http://www.w3.org/2001/XMLSchema" 
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
  xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" 
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" 
  xmlns:tns="WSWebService" name="WSWebService" targetNamespace="WSWebService">
  <wsdl:types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="WSWebService">
      <s:complexType name="WSUserLoginRequest">
        <s:sequence>
          <s:element name="userName" type="s:string" />
          <s:element name="userPassword" type="s:string" />
        </s:sequence>
      </s:complexType>
      <s:complexType name="WSUserLoginResponse">
        <s:sequence>
          <s:element name="userToken" type="s:string" />
          <s:element name="wsdlVersion" type="s:string" minOccurs="1" maxOccurs="1" default="2.0.0.0" />
          <s:element name="result" type="s:int" />
          <s:element name="resultString" type="s:string" />
        </s:sequence>
      </s:complexType>
    </schema>
  </wsdl:types>
  <wsdl:message name="WSUserLoginSoapIn">
    <wsdl:part name="parameters" type="tns:WSUserLoginRequest" />
  </wsdl:message>
  <wsdl:message name="WSUserLoginSoapOut">
    <wsdl:part name="parameters" type="tns:WSUserLoginResponse" />
  </wsdl:message>
  <wsdl:portType name="WSWebServiceSoapPort">
    <wsdl:operation name="WSUserLogin">
      <wsdl:documentation>Documentation</wsdl:documentation>
      <wsdl:input message="tns:WSUserLoginSoapIn" />
      <wsdl:output message="tns:WSUserLoginSoapOut" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="WSWebServiceSoapBinding" type="tns:WSWebServiceSoapPort">
    <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="WSUserLogin">
      <soap:operation soapAction="WSUserLogin" style="rpc" />
      <wsdl:input>
        <soap:body use="literal" namespace="WSWebService" />
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal" namespace="WSWebService" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="WSWebService">
    <wsdl:port name="WSWebServiceSoapPort" binding="tns:WSWebServiceSoapBinding">
      <soap:address location="https://x.x.x.x:x" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

Solution

  • I used your wsdl file to generate the c# code using dotnet-svcutil 2.0.1 as you did.

    I mocked a SOAP endpoint using SoapUI mocking service, fired it, and then ran your code on my machine (connecting to the mocked endpoint on my localhost).

    I got an exception early on calling proxy.OpenAsync. The exception message I got was:

    The top XML element 'parameters' from namespace '' references distinct types WSUserLoginRequest and WSUserLoginResponse. Use XML attributes to specify another XML name or namespace for the element or types.

    So I went to WSUserLoginRequest1 inside the generated code and added a value to the Namespace of parameters, as explained here:

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1")]
    [System.ServiceModel.MessageContractAttribute(WrapperName="WSUserLogin", WrapperNamespace="WSWebService", IsWrapped=true)]
    public partial class WSUserLoginRequest1
    {
        // replaced Namespace empty string value with my endpoint url
        [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://localhost:8181/WSUserLogin", Order=0)]
        public WSUserLoginRequest parameters;
    
        public WSUserLoginRequest1()
        {
        }
    
        // ...
    }
    

    After that change, it just worked, and I got back a response when calling proxy.WSUserLoginAsync.

    I wish I knew how to add that Namespace value by passing values to the /namespace option of dotnet-svcutil, but following these SO posts (1, 2, though they relate to svcutil, not dotnet-svcutil) only added namespaces to other places in the generated file.

    Additionaly, as you noted, using wsdl.exe doesn't generate the unneccesary WSUserLoginResponse1, while svcutil-dotnet does. This seems to be a known problem, see e.g. here: svcutil generated unneccesary wrapper classes.

    I hope it would be of any value for your problem.