dotnet-svcutil-generated WCF 代理(甚至自定义 Channel<>)正在为有效的 SOAP 响应返回 null

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

使用 dotnet-svcutil 2.0.1 (dotnet-svcutil --sync --outputDir . http://XXX/?WSDL) 和 System.ServiceModel.* 4.7.0 生成的代理,调用代码和下面的 WSDL。代理无法反序列化有效响应,只是 returns null。在 Windows 10 和 macOS Catalina 上尝试了 .NET Core 3.0 和 3.1,同样的空结果。 Fiddler 请求和响应随附于来自服务器的 WSDL(我无法控制的服务器)。

对于代理,我正在使用@shmao 的set_mode 解决方法(https://github.com/dotnet/wcf/issues/2219) 来避免'JScript/CSharp scripts is not supported' 异常。此外,我必须删除 Namespace="" 属性才能使请求部分正常工作。我添加了 EventListeners 并从所有事件源中将所有内容转储到 Verbose,没有 warnings/errors,只是 null。

我也尝试了 Channel-based MessageContract/DataContract 方法,最终得到相同的空结果(!),我无法利用任何 .NET Core WCF-based 代码来反序列化给定的响应.

将考虑使用 .NET Core 3.1 WCF 反序列化给定响应的任何解决方案甚至部分解决方案,最好使用 dotnet-svcutil。发现 warning/error 甚至手动访问响应字符串仍然是对 non-WCF string/HttpRequest-based 方法的改进。

      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;
      }

来自服务器的相关 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>

      ...

      <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>

(编辑)

WSUserLoginResponse1 的 class 定义由 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;
  }
}

(编辑 2) 根据建议来自 dotnet-svcutil 的 WSUserLoginResponse。

/// <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;
    }
  }
}

(编辑 3) 根据建议来自 wsdl.exe 的 WSUserLoginResponse。没有 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));
            }
        }
    }

(编辑 4) 独立 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>

我使用您的 wsdl 文件使用 dotnet-svcutil 2.0.1 生成了 c# 代码。

我使用 SoapUI mocking service 模拟了一个 SOAP 端点,触发了它,然后 运行 你的代码在我的机器上(连接到我本地主机上的模拟端点)。

我在调用 proxy.OpenAsync 时遇到异常。我收到的异常消息是:

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.

所以我转到生成代码中的 WSUserLoginRequest1 并向 parametersNamespace 添加了一个值,如 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()
    {
    }

    // ...
}

更改之后,它就起作用了,我在调用 proxy.WSUserLoginAsync 时得到了响应。

我希望我知道如何通过将值传递给 dotnet-svcutil/namespace 选项来添加 Namespace 值,但是遵循这些 SO 帖子(1, 2,虽然它们与 svcutil 相关,而不是 dotnet-svcutil) 仅将名称空间添加到生成文件中的其他位置。

此外,如您所述,使用 wsdl.exe 不会生成不必要的 WSUserLoginResponse1,而 svcutil-dotnet 会生成。这似乎是一个已知问题,参见例如这里:svcutil generated unneccesary wrapper classes.

希望对您的问题有所帮助。