在 .net Core 中调用 SOAP 服务

Posted

技术标签:

【中文标题】在 .net Core 中调用 SOAP 服务【英文标题】:Calling a SOAP service in .net Core 【发布时间】:2018-07-15 11:29:32 【问题描述】:

我正在将 .net 4.6.2 代码移植到调用 SOAP 服务的 .net Core 项目。在新代码中,我使用的是 C#(由于某些配置原因,我现在不记得为什么了)。

但我遇到了以下异常。

接收到https://someurl.com/ws/Thing.pub.ws:Something 的 HTTP 响应时出错。 这可能是由于服务端点绑定未使用 HTTP 协议。 这也可能是由于服务器中止了 HTTP 请求上下文(可能是由于服务关闭)。 有关详细信息,请参阅服务器日志。

抛出它的代码是

try

    var binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

    var endpoint = new EndpointAddress(new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService"));

    var thing= new TheEndpoint.AService_PortTypeClient(binding, endpoint);
    thing.ClientCredentials.UserName.UserName = "usrn";
    thing.ClientCredentials.UserName.Password = "passw";

    var response = await thing.getSomethingAsync("id").ConfigureAwait(false);


finally

    await thing.CloseAsync().ConfigureAwait(false);

基于调用服务的旧配置是这样的,我错过了什么

<bindings>
  <basicHttpsBinding>
    <binding name="TheEndpoint_pub_ws_AService_Binder" closeTimeout="00:02:00"
        openTimeout="00:02:00" receiveTimeout="00:03:00" sendTimeout="00:03:00">
      <security mode="Transport">
        <transport clientCredentialType="Basic" />
        <message clientCredentialType="UserName" algorithmSuite="Default" />
      </security>
    </binding>
  </basicHttpsBinding>
</bindings>
<client>
  <endpoint address="https://someurl.com/ws/Thing.pub.ws:AService"
      binding="basicHttpsBinding" bindingConfiguration="TheEndpoint_pub_ws_AService_Binder"
      contract="TheEndpoint.AService_PortType" name="TheEndpoint_pub_ws_AService_Port" />
</client>

我只是无法在网上找到很多关于此的信息。希望你能帮助我。

更新 根据 Sixto Saez 的建议,我得到了端点来揭示它的错误,它是

使用客户端身份验证方案“基本”未授权 HTTP 请求。从服务器收到的身份验证标头是 'Basic realm="Integration Server", encoding="UTF-8"'。

如果成功,我会尝试找出该怎么做并在此处发布结果。

更新 2

好的,现在我尝试使用这里的代码转移到新语法

ChannelFactory<IAService> factory = null;
IAService serviceProxy = null;
Binding binding = null;

try

   binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);

   factory = new ChannelFactory<IAService>(binding, new EndpointAddress(new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService")));            
   factory.Credentials.UserName.UserName = "usrn";
   factory.Credentials.UserName.Password = "passw";

   serviceProxy = factory.CreateChannel();

   var result = await serviceProxy.getSomethingAsync("id").ConfigureAwait(false);

    factory.Close();
    ((ICommunicationObject)serviceProxy).Close();  

catch (MessageSecurityException ex)

    //error caught here
    throw;

但我仍然遇到相同(略有不同)的错误。它现在有 'Anonymous' 而不是 'Basic' 并且现在缺少最后的 ", encoding="UTF-8"。

HTTP 请求未经客户端身份验证方案“匿名”授权。从服务器收到的身份验证标头是“Basic realm="Integration Server"”。

是我的问题还是服务器的问题?

显然,我现在的 SOAP“技能”非常缺乏,但我几乎尝试了所有我能想到的使用这种新方法的配置组合,但没有运气。希望有人能指出我正确的方向。

【问题讨论】:

该错误似乎是服务器端问题。在客户端,不清楚Thing.Authorization_PortTypeClient 类是否封装了ChannelFactory 的创建和Channel 实例的生成。请参阅此test code,了解如何在 .NET Core 中创建和调用 WCF 客户端。这个GitHub repo for WCF client libraries 可能包含您正在寻找的文件,文档方面。 谢谢@SixtoSaez。我更新了问题。但现在我得到另一个错误,我只是无法找出真正的意思。我已经联系了服务器人员,以检查他们是否有问题,但是如果您有任何见解,我将不胜感激。 查看这个答案***.com/a/52696777/5077196 【参考方案1】:

好的,此答案适用于那些试图从 .net Core 项目连接到 WCF 服务的人。

这是我的问题的解决方案,使用新的 .net Core WCF 语法/库。

BasicHttpBinding basicHttpBinding = null;
EndpointAddress endpointAddress = null;
ChannelFactory<IAService> factory = null;
IAService serviceProxy = null;

try

    basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
    basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
    endpointAddress = new EndpointAddress(new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService"));
    factory = new ChannelFactory<IAService>(basicHttpBinding, endpointAddress);

    factory.Credentials.UserName.UserName = "usrn";
    factory.Credentials.UserName.Password = "passw";
    serviceProxy = factory.CreateChannel();

    using (var scope = new OperationContextScope((IContextChannel)serviceProxy))
    
        var result = await serviceProxy.getSomethingAsync("id").ConfigureAwait(false);
    

    factory.Close();
    ((ICommunicationObject)serviceProxy).Close();

catch (MessageSecurityException ex)

     throw;

catch (Exception ex)

    throw;

finally

    // *** ENSURE CLEANUP (this code is at the WCF GitHub page *** \\
    CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);

更新

我使用上面的代码得到了以下异常

这个 OperationContextScope 正在被乱序处理。

WCF 团队似乎是 something that is broken(或需要解决)。

所以我必须执行以下操作才能使其工作(基于此GitHub issue)

basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

factory = new ChannelFactory<IAService_PortType>(basicHttpBinding, new EndpointAddress(new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService")));
factory.Credentials.UserName.UserName = "usern";
factory.Credentials.UserName.Password = "passw";
serviceProxy = factory.CreateChannel();
((ICommunicationObject)serviceProxy).Open();
var opContext = new OperationContext((IClientChannel)serviceProxy);
var prevOpContext = OperationContext.Current; // Optional if there's no way this might already be set
OperationContext.Current = opContext;

try

    var result = await serviceProxy.getSomethingAsync("id").ConfigureAwait(false);

    // cleanup
    factory.Close();
    ((ICommunicationObject)serviceProxy).Close();

finally

  // *** ENSURE CLEANUP *** \\
  CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
  OperationContext.Current = prevOpContext; // Or set to null if you didn't capture the previous context

但您的要求可能会有所不同。因此,您可能需要以下资源来帮助您连接到 WCF 服务:

WCF .net core at GitHub BasicHttpBinding Tests ClientCredentialType Tests

测试对我帮助很大,但有些地方很难找到(我得到了帮助,感谢甄兰answering my wcf github issue)

【讨论】:

什么是 IAService? I-A-Service 只是一个与描述 WCF 服务的服务模型合同的接口,请查看docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/… 对于在寻找解决方案时发现此问题的任何其他人。 @Sturla 提到的接口 IAService_PortType 是与您的 web 服务匹配的接口。如果您按照本指南生成joshuachini.com/2017/07/13/… - 该接口将存在于您在生成期间指定的命名空间内。 我收到以下错误,请:序列化消息正文时出错:“生成 XML 文档时出错。”。有关详细信息,请参阅 InnerException。【参考方案2】:

要从 .NET 核心使用 SOAP 服务,从项目 UI 添加连接的服务不起作用。

选项 1: 使用 dotnet-svcutil CLI。 先决条件:VS 2017,版本 15.5 或以上

    启动开发人员命令提示符 VS 2017。

    转到 app.csproj 文件并添加以下引用:

    <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
    <PackageReference Include="System.ServiceModel.Http" Version="4.5.3" />
    </ItemGroup>
    <ItemGroup>
    <DotNetCliToolReference Include="dotnet-svcutil" Version="1.0.*" />
    </ItemGroup>
    

    重建解决方案。

    从 VS 命令提示符将目录更改为您的项目位置。 运行命令:svcutil SOAP_URL?wsdl; 示例:example.com/test/testing?wsdl 这将在您的项目中生成参考文件和 output.config 文件。 .Net Core 没有任何 app.config 或 web.config 文件,但 output.config 文件将为 SOAP 绑定提供服务。

选项 2 如果您需要引用多个 SOAP 服务,

    新建类库项目,使用.Net framework 4.5.1 .Net 框架很重要,因为我看到了从合同生成的参考文件 如果 .Net Framework 是最新的,则不正确。 通过右键单击引用添加服务引用。 从您的 .Net 核心项目中引用类库项目。

【讨论】:

我很确定我用这个扩展来做docs.microsoft.com/en-us/dotnet/core/additional-tools/… 如果它是一个 .NET Standard 类库怎么办? Microsoft.AspNetCore.All 在这种情况下无法添加... @Justin ,对于您的情况,上面提到的选项 2 适用。 new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential) 这不起作用,问题是通过 WCF 与 TransportWithMessageCredential 建立连接。 版本已更新,4.6.0 与 TransportWithMessageCredential &lt;ItemGroup&gt; &lt;PackageReference Include="System.ServiceModel.Duplex" Version="4.6.0" /&gt; &lt;PackageReference Include="System.ServiceModel.Http" Version="4.6.0" /&gt; &lt;PackageReference Include="System.ServiceModel.NetTcp" Version="4.6.0" /&gt; &lt;PackageReference Include="System.ServiceModel.Security" Version="4.6.0" /&gt; &lt;/ItemGroup&gt; 配合得很好【参考方案3】:

所以我不得不这样做并使用WCF Web Service Reference Provider Tool。

根据此处类似的回复,显然需要使用 Bindings、Factories 和 Proxies 进行所有迂回业务似乎很奇怪,考虑到这一切似乎都是导入类的一部分。

找不到简单的官方“HowTo”,我将发布我的发现,说明我能够拼凑出的最简单设置,以满足我对 Digest 身份验证的要求:

    ServiceName_PortClient client = new ServiceName_PortClient();
    //GetBindingForEndpoint returns a BasicHttpBinding
    var httpBinding = client.Endpoint.Binding as BasicHttpBinding;
    httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Digest;
    client.ClientCredentials.HttpDigest.ClientCredential = new NetworkCredential("Username", "Password", "Digest");
    var result = await client.GetResultAsync();

现在,如果您不需要进行任何身份验证,只需执行以下操作:

    ServiceName_PortClient client = new ServiceName_PortClient();
    var result = await client.GetResultAsync();

应该足够了。

ServiceName_PortClient 类是由导入工具生成的,其中ServiceName 是我要导入的服务的名称。

当然,将配置放置在部分ServiceName_PortClient 类中似乎更符合导入代码的精神:

    public partial class ServiceName_PortClient
    
        static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials)
        
            var httpBinding = serviceEndpoint.Binding as BasicHttpBinding;
            httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Digest;
            clientCredentials.HttpDigest.ClientCredential = new NetworkCredential("Username", "Password", "Realm");
        
    

【讨论】:

如果需要添加 Bearer 令牌怎么办?【参考方案4】:

对于那些尝试对 NTLM 和 .Net Core 执行相同操作并想知道某些变量定义为什么的人,我将代码澄清为如下所示:

IAService_PortType 是您按照https://joshuachini.com/2017/07/13/calling-a-soap-service-from-asp-net-core-or-net-core/ 上的指南创建的服务参考

BasicHttpBinding basicHttpBinding = 
    new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
// Setting it to Transport will give an exception if the url is not https
basicHttpBinding.Security.Transport.ClientCredentialType = 
    HttpClientCredentialType.Ntlm;

ChannelFactory<IAService_PortType> factory = 
    new ChannelFactory<IAService_PortType>(basicHttpBinding, 
    new EndpointAddress(
        new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService")));
factory.Credentials.Windows.ClientCredential.Domain = domain;
factory.Credentials.Windows.ClientCredential.UserName = user;
factory.Credentials.Windows.ClientCredential.Password = pass;
IAService_PortType serviceProxy = factory.CreateChannel();
((ICommunicationObject)serviceProxy).Open();

try

    var result = serviceProxy.getSomethingAsync("id").Result;


finally

    // cleanup
    factory.Close();
    ((ICommunicationObject)serviceProxy).Close();

【讨论】:

【参考方案5】:

您甚至可以在纯dotnet core 中使用ChannelFactoryclass。这很简单。

var binding = new BasicHttpsBinding();
var endpoint = new EndpointAddress(new Uri("https://<myhost>/SimpleSOAPService.svc"));
dynamic channelFactory = new ChannelFactory<ISimpleSOAPService>(binding, endpoint);
var serviceClient = channelFactory.CreateChannel();
var result = serviceClient.Ping();
channelFactory.Close();

您可以找到一个有效的 GitHub 示例 here。

不要忘记在BasicHttpBindingBasicHttpsBinding 之间进行切换,具体取决于您在URL 中使用的是HTTP 还是HTTPS

【讨论】:

【参考方案6】:

我只在 unix(Docker 和 wsl2)环境中遇到了与 There was an error reflecting 'login' 相同的问题。 在 Windows 上完全没有问题。

我尝试通过dotnet-svcutil(版本2.0.1)创建连接服务,但这并没有解决问题。

我终于找到了问题的根源。 我使用以下 dotnet 命令发布了我的应用程序: dotnet publish "myapp/myapp.csproj" -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=true -p:ReadyToRun=true -p:ReadyToRunShowWarnings=true -o publish

当我删除属性 -p:PublishTrimmed=true 后,它终于在 wsl2 和我的 docker 环境下都可以工作了。

【讨论】:

【参考方案7】:

我使用 .net core 3 svutil 从老式 asmx SOAP / WSDL 生成包装类。见-https://docs.microsoft.com/en-us/dotnet/core/additional-tools/dotnet-svcutil-guide?tabs=dotnetsvcutil2x

然后我包装了遥控器并使用下面的代码来设置身份验证。我相信这适用于 Kerberos,并将回退到 NTLM(取决于从服务器返回的标头)。

public class DocumentManagerWrapper

    public DocumentManagerWrapper(string serviceUrl,
        String username,
        String password)
    
        _serviceUrl = serviceUrl;
        _username = username;
        _password = password;
    
    private String _serviceUrl;
    private String _username;
    private String _password;

    private DocumentManagerSoap GetSoapConnection()
    
        BasicHttpSecurityMode mode = _serviceUrl.StartsWith("https") ? BasicHttpSecurityMode.Transport : BasicHttpSecurityMode.TransportCredentialOnly;
        BasicHttpBinding binding = new BasicHttpBinding(mode);

        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

        EndpointAddress address = new EndpointAddress(_serviceUrl);

        ChannelFactory<DocumentManagerSoapChannel> channel = new ChannelFactory<DocumentManagerSoapChannel>(binding, address);
        channel.Credentials.Windows.ClientCredential.UserName = _username;
        channel.Credentials.Windows.ClientCredential.Password = _password;
        DocumentManagerSoap soap = channel.CreateChannel();
    
        return soap;
     

注意 - DocumentManagerSoapChannelDocumentManagerSoap 类是由 svcutil 生成的。

【讨论】:

以上是关于在 .net Core 中调用 SOAP 服务的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 net core / net 5 连接 SOAP 服务

从.NET调用WCF服务时如何以编程方式添加soap标头?

在 .NET Compact Framework 中使用简单的 SOAP

如何创建 PHP SOAP 客户端以在 SSL 下调用 WCF Web 服务?

C#实现SOAP调用WebService

强制在来自 NET Web 服务代理类的 SOAP 请求中包含默认值属性属性