简体   繁体   English

在 .net Core 中调用 SOAP 服务

[英]Calling a SOAP service in .net Core

I´m porting a .net 4.6.2 code to a .net Core project , that calls a SOAP service.我正在将 .net 4.6.2 代码移植到调用 SOAP 服务的.net Core 项目 In the new code I´m using C# (because of some config reasons I just can´t remember why right now).在新代码中,我使用 C#(由于某些配置原因,我现在不记得为什么了)。

But I´m getting the following exception.但我收到以下异常。

An error occurred while receiving the HTTP response to https://someurl.com/ws/Thing.pub.ws:Something .接收对https://someurl.com/ws/Thing.pub.ws:Something的 HTTP 响应时出错。 This could be due to the service endpoint binding not using the HTTP protocol.这可能是由于服务端点绑定未使用 HTTP 协议。 This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down).这也可能是由于服务器中止了 HTTP 请求上下文(可能是由于服务关闭)。 See server logs for more details.有关更多详细信息,请参阅服务器日志。

The code that is throwing it is抛出它的代码是

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

Based on the old config where it works calling the service is like this, what am I missing ?基于它调用服务的旧配置是这样的,我错过了什么?

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

I´m just unable to find lot of information on this online.我只是无法在网上找到很多关于此的信息。 Hope you can help me.希望你能帮助我。

UPDATE Per Sixto Saez suggestion I got the endpoint to reveal its error and it is根据 Sixto Saez 的建议更新我得到了端点来揭示它的错误,它是

The HTTP request is unauthorized with client authentication scheme 'Basic'. HTTP 请求未经授权,客户端身份验证方案为“基本”。 The authentication header received from the server was 'Basic realm="Integration Server", encoding="UTF-8"'.从服务器收到的身份验证标头是“基本领域=“集成服务器”,编码=“UTF-8””。

I´ll try to find out what to do and post the result here if successful.如果成功,我会尝试找出该怎么做并在此处发布结果。

UPDATE 2更新 2

Ok now I tried to move to the new syntax with this code here好的,现在我尝试使用此代码移至新语法here

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

but I still get the same (slightly different) error.但我仍然得到相同(略有不同)的错误。 It now has 'Anonymous' instead of 'Basic' and is now missing ", encoding="UTF-8" at the end.它现在有 'Anonymous' 而不是 'Basic' 并且现在在末尾缺少 ", encoding="UTF-8"。

The HTTP request is unauthorized with client authentication scheme 'Anonymous'. HTTP 请求未经授权,客户端身份验证方案为“匿名”。 The authentication header received from the server was 'Basic realm="Integration Server"'.从服务器收到的身份验证标头是“基本领域=“集成服务器””。

Is the problem at my side or the servers?是我这边的问题还是服务器的问题?

Obviously my SOAP "skills" are greatly lacking now days, but I have just about tried every config combo I can think of with this new approach without luck.显然,我的 SOAP“技能”现在非常缺乏,但我几乎尝试了我能想到的所有配置组合,但没有运气。 Hope somebody can point me in the right direction.希望有人能指出我正确的方向。

Ok this answer is for those who are trying to connect to a WCF service from a .net Core project.好的,这个答案适用于那些试图从 .net Core项目连接到 WCF服务的人。

Here is the solution to my problem, using the new .net Core WCF syntax/library.这是我的问题的解决方案,使用新的 .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);
}

UPDATE更新

I got the following exception using the code above我使用上面的代码得到以下异常

This OperationContextScope is being disposed out of order.此 OperationContextScope 被无序处理。

Which seems to be something that is broken (or needs addressing) by the WCF team.这似乎WCF 团队破坏(或需要解决)的问题。

So I had to do the following to make it work (based on this GitHub issue )因此,我必须执行以下操作才能使其正常工作(基于此GitHub 问题

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
}

But your requirements will probably be different.但是您的要求可能会有所不同。 So here are the resources you might need to help you connecting to your WCF service are here:因此,以下是帮助您连接到 WCF 服务可能需要的资源:

The tests helped me a lot but they where somewhat hard to find (I had help, thank you Zhenlan for answering my wcf github issue )这些测试对我帮助很大,但有些地方很难找到(我得到了帮助,感谢振兰回答我的 wcf github 问题

To consume a SOAP service from .NET core, adding connected service from the project UI does not work.要从 .NET Core 使用 SOAP 服务,从项目 UI 添加连接的服务不起作用。

Option 1: Use dotnet-svcutil CLI.选项 1:使用 dotnet-svcutil CLI。 Prerequisite: VS 2017, Version 15.5 or above先决条件:VS 2017,15.5或以上版本

  1. Launch Developer Command Prompt VS 2017.启动开发人员命令提示符 VS 2017。
  2. Go to app.csproj file and add below references:转到 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>
  3. Rebuild solution.重建解决方案。

  4. Change directory to your project location from VS command prompt.从 VS 命令提示符将目录更改为您的项目位置。
  5. run command: svcutil SOAP_URL?wsdl ;运行命令:svcutil SOAP_URL?wsdl ; example: example.com/test/testing?wsdl This will generate reference files and output.config file in your project.示例:example.com/test/testing?wsdl 这将在您的项目中生成参考文件和 output.config 文件。
  6. .Net Core does not have any app.config or web.config files, but the output.config file will serve the SOAP binding. .Net Core 没有任何 app.config 或 web.config 文件,但 output.config 文件将为 SOAP 绑定提供服务。

Option 2 In case you need to refer more than one SOAP sevice,选项 2如果您需要参考多个 SOAP 服务,

  1. Create a new class library project, use .Net framework 4.5.1 .Net framework matters as i saw the reference files generated from contract is not correct if .Net Framework is latest.创建一个新的类库项目,使用 .Net framework 4.5.1 .Net framework 很重要,因为我看到如果 .Net Framework 是最新的,则从合同生成的参考文件不正确。
  2. Add service reference by right click on References.通过右键单击“引用”添加服务引用。
  3. Refer the class library project from your .Net core project.请参阅 .Net 核心项目中的类库项目。

So I had to do this and used the WCF Web Service Reference Provider Tool .所以我不得不这样做并使用了WCF Web Service Reference Provider Tool

The apparent need, according to responses like those here, for all the roundabout business with Bindings and Factories and Proxies seemed strange, considering that this all appeared to be part of the imported class.根据这里的回应,对于所有与绑定、工厂和代理有关的迂回业务的明显需求似乎很奇怪,考虑到这一切似乎都是导入类的一部分。

Not being able to find a straightforward official "HowTo", I will post my findings as to the simplest setup I was able to cobble together to fit my requirements with Digest authentication:无法找到一个简单的官方“HowTo”,我将发布我的发现,关于我能够拼凑起来以满足我对摘要式身份验证的要求的最简单设置:

    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();

Now, if you don't need to do any authentication simply doing:现在,如果您不需要进行任何身份验证,只需执行以下操作:

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

Should be sufficient.应该够了。

The ServiceName_PortClient class was generated as such by the import tool, where ServiceName was the name of the service I was importing. ServiceName_PortClient类是由导入工具生成的,其中ServiceName是我要导入的服务的名称。

Of course it seems to be more in the spirit of the imported code to place the configuration in a partial ServiceName_PortClient class along the lines of:当然,将配置放置在部分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");
        }
    }

For those who are trying to do the same with NTLM and .Net Core and wondering what some of the variables are defined as, I clarified the code to look like:对于那些试图用 NTLM 和 .Net Core 做同样的事情并想知道一些变量被定义为什么的人,我澄清了代码如下:

IAService_PortType is the service reference you created if you followed the guide on https://joshuachini.com/2017/07/13/calling-a-soap-service-from-asp-net-core-or-net-core/如果您遵循https://joshuachini.com/2017/07/13/calling-a-soap-service-from-asp-net-core-or-net-core/上的指南,则IAService_PortType是您创建的服务引用

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

You can use the ChannelFactory class even in pure dotnet core .您甚至可以在纯dotnet core使用ChannelFactory类。 It is fairly simple.这相当简单。

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();

You can find a working GitHub example here .您可以在此处找到一个可用的 GitHub 示例。

Don't forget to change between BasicHttpBinding and BasicHttpsBinding depending on whether you are using HTTP or HTTPS in the URL .不要忘记根据您在URL中使用HTTP还是HTTPSBasicHttpBindingBasicHttpsBinding之间进行更改。

I had the same problem with There was an error reflecting 'login' only in a unix (Docker and wsl2) environment.我遇到了同样的问题,仅在 unix(Docker 和 wsl2)环境中There was an error reflecting 'login' On Windows there was no problem at all.在 Windows 上根本没有问题。

I tried to create the connected service via the dotnet-svcutil (version 2.0.1), but this didn't solve the issue.我尝试通过dotnet-svcutil (版本 2.0.1)创建连接的服务,但这并没有解决问题。

I finally managed to find the root of my problem.我终于找到了问题的根源。 I published my application with the following dotnet command: dotnet publish "myapp/myapp.csproj" -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=true -p:ReadyToRun=true -p:ReadyToRunShowWarnings=true -o publish我使用以下 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

When I removed the attribute -p:PublishTrimmed=true it finally worked both under wsl2 and in my docker environment.当我删除属性-p:PublishTrimmed=true它最终在 wsl2 和我的 docker 环境中都可以工作。

I used the .net core 3 svutil to generate the wrapper classes from an old school asmx SOAP / WSDL.我使用 .net core 3 svutil 从老式的 asmx SOAP/WSDL 生成包装类。 See - https://docs.microsoft.com/en-us/dotnet/core/additional-tools/dotnet-svcutil-guide?tabs=dotnetsvcutil2x请参阅 - https://docs.microsoft.com/en-us/dotnet/core/additional-tools/dotnet-svcutil-guide?tabs=dotnetsvcutil2x

I then wrapped the remote and used the code below to set the authentication.然后我包装了遥控器并使用下面的代码来设置身份验证。 I believe this works for Kerberos and will fall back to NTLM (depending on the headers returned from the server).我相信这适用于 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;
     }
}

Note - DocumentManagerSoapChannel and DocumentManagerSoap classes are generated by svcutil.注 - DocumentManagerSoapChannelDocumentManagerSoap类由 svcutil 生成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM