在 .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 <ItemGroup> <PackageReference Include="System.ServiceModel.Duplex" Version="4.6.0" /> <PackageReference Include="System.ServiceModel.Http" Version="4.6.0" /> <PackageReference Include="System.ServiceModel.NetTcp" Version="4.6.0" /> <PackageReference Include="System.ServiceModel.Security" Version="4.6.0" /> </ItemGroup>
配合得很好【参考方案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
中使用ChannelFactory
class。这很简单。
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。
不要忘记在BasicHttpBinding
和BasicHttpsBinding
之间进行切换,具体取决于您在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;
注意 - DocumentManagerSoapChannel 和 DocumentManagerSoap 类是由 svcutil 生成的。
【讨论】:
以上是关于在 .net Core 中调用 SOAP 服务的主要内容,如果未能解决你的问题,请参考以下文章
尝试使用 net core / net 5 连接 SOAP 服务
在 .NET Compact Framework 中使用简单的 SOAP