如何记录 SOAP 请求?
How do I log SOAP requests?
正如我们大多数人已经知道的那样,在 Visual Studio 2017 年,您可以添加一个 Connected Service
并使用相关的 WSDL 将其配置为指向您想要的 SOAP 服务。这会在 Reference.cs
文件中创建一组代理 classes,用于与相关 SOAP 服务进行交互。
有没有办法在某个时刻拦截转换后的 SOAP
过程 - 以编程方式 - 所以我们可以知道到底是什么
发送到目标服务?我问这个是因为我需要能够记录发送出去的实际 SOAP,所以使用单独的应用程序(例如 Fiddler)不是一个选项。
我仔细研究了 Reference.cs
文件中的代码,一切都太抽象了,以至于我无法判断转换实际发生的位置。
为了本次讨论,我使用了一个非常简单的免费 SOAP 服务,位于:http://www.dneonline.com/calculator.asmx
生成的 Reference.cs
文件的内容显示在此 post 的末尾。在该代码中,您可以看到客户端 class 的方法 AddAsync(...)
,例如,调用 base.Channel.AddAsync(intA, intB)
,一个在 CalculatorSoap
接口中定义的方法。那么,如果 Channel
是一个 CalculatorSoap
接口,那么实际使用的具体 class 在哪里呢?这是我所说的如此抽象的普遍存在的一个例子。
我很高兴(也很感激)听到你们任何人关于这里发生的事情的任何想法。
谢谢。
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// //
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CalculatorSoapServiceReference
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="CalculatorSoapServiceReference.CalculatorSoap")]
public interface CalculatorSoap
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Add", ReplyAction="*")]
System.Threading.Tasks.Task<int> AddAsync(int intA, int intB);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Subtract", ReplyAction="*")]
System.Threading.Tasks.Task<int> SubtractAsync(int intA, int intB);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Multiply", ReplyAction="*")]
System.Threading.Tasks.Task<int> MultiplyAsync(int intA, int intB);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Divide", ReplyAction="*")]
System.Threading.Tasks.Task<int> DivideAsync(int intA, int intB);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
public interface CalculatorSoapChannel : CalculatorSoapServiceReference.CalculatorSoap, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
public partial class CalculatorSoapClient : System.ServiceModel.ClientBase<CalculatorSoapServiceReference.CalculatorSoap>, CalculatorSoapServiceReference.CalculatorSoap
{
/// <summary>
/// Implement this partial method to configure the service endpoint.
/// </summary>
/// <param name="serviceEndpoint">The endpoint to configure</param>
/// <param name="clientCredentials">The client credentials</param>
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
public CalculatorSoapClient(EndpointConfiguration endpointConfiguration) :
base(CalculatorSoapClient.GetBindingForEndpoint(endpointConfiguration), CalculatorSoapClient.GetEndpointAddress(endpointConfiguration))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public CalculatorSoapClient(EndpointConfiguration endpointConfiguration, string remoteAddress) :
base(CalculatorSoapClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public CalculatorSoapClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) :
base(CalculatorSoapClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public CalculatorSoapClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public System.Threading.Tasks.Task<int> AddAsync(int intA, int intB)
{
return base.Channel.AddAsync(intA, intB);
}
public System.Threading.Tasks.Task<int> SubtractAsync(int intA, int intB)
{
return base.Channel.SubtractAsync(intA, intB);
}
public System.Threading.Tasks.Task<int> MultiplyAsync(int intA, int intB)
{
return base.Channel.MultiplyAsync(intA, intB);
}
public System.Threading.Tasks.Task<int> DivideAsync(int intA, int intB)
{
return base.Channel.DivideAsync(intA, intB);
}
public virtual System.Threading.Tasks.Task OpenAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndOpen));
}
public virtual System.Threading.Tasks.Task CloseAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginClose(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndClose));
}
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap12))
{
System.ServiceModel.Channels.CustomBinding result = new System.ServiceModel.Channels.CustomBinding();
System.ServiceModel.Channels.TextMessageEncodingBindingElement textBindingElement = new System.ServiceModel.Channels.TextMessageEncodingBindingElement();
textBindingElement.MessageVersion = System.ServiceModel.Channels.MessageVersion.CreateVersion(System.ServiceModel.EnvelopeVersion.Soap12, System.ServiceModel.Channels.AddressingVersion.None);
result.Elements.Add(textBindingElement);
System.ServiceModel.Channels.HttpTransportBindingElement httpBindingElement = new System.ServiceModel.Channels.HttpTransportBindingElement();
httpBindingElement.AllowCookies = true;
httpBindingElement.MaxBufferSize = int.MaxValue;
httpBindingElement.MaxReceivedMessageSize = int.MaxValue;
result.Elements.Add(httpBindingElement);
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap))
{
return new System.ServiceModel.EndpointAddress("http://www.dneonline.com/calculator.asmx");
}
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap12))
{
return new System.ServiceModel.EndpointAddress("http://www.dneonline.com/calculator.asmx");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
public enum EndpointConfiguration
{
CalculatorSoap,
CalculatorSoap12,
}
}
}
对于第 3 个问题,您可以使用 Message Inspector 扩展。有details here on how to set it up
使用消息检查器,您可以查看、保留和操作关于传出请求、传入回复、传入请求和传出回复的 SOAP 消息。
关于内部工作,WCF 使用 Message 对象来处理 SOAP 信封。你可以阅读更多about that here
消息是通道模型的一部分,相当bit of info here
正如我们大多数人已经知道的那样,在 Visual Studio 2017 年,您可以添加一个 Connected Service
并使用相关的 WSDL 将其配置为指向您想要的 SOAP 服务。这会在 Reference.cs
文件中创建一组代理 classes,用于与相关 SOAP 服务进行交互。
有没有办法在某个时刻拦截转换后的 SOAP 过程 - 以编程方式 - 所以我们可以知道到底是什么 发送到目标服务?我问这个是因为我需要能够记录发送出去的实际 SOAP,所以使用单独的应用程序(例如 Fiddler)不是一个选项。
我仔细研究了 Reference.cs
文件中的代码,一切都太抽象了,以至于我无法判断转换实际发生的位置。
为了本次讨论,我使用了一个非常简单的免费 SOAP 服务,位于:http://www.dneonline.com/calculator.asmx
生成的 Reference.cs
文件的内容显示在此 post 的末尾。在该代码中,您可以看到客户端 class 的方法 AddAsync(...)
,例如,调用 base.Channel.AddAsync(intA, intB)
,一个在 CalculatorSoap
接口中定义的方法。那么,如果 Channel
是一个 CalculatorSoap
接口,那么实际使用的具体 class 在哪里呢?这是我所说的如此抽象的普遍存在的一个例子。
我很高兴(也很感激)听到你们任何人关于这里发生的事情的任何想法。
谢谢。
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// //
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CalculatorSoapServiceReference
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="CalculatorSoapServiceReference.CalculatorSoap")]
public interface CalculatorSoap
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Add", ReplyAction="*")]
System.Threading.Tasks.Task<int> AddAsync(int intA, int intB);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Subtract", ReplyAction="*")]
System.Threading.Tasks.Task<int> SubtractAsync(int intA, int intB);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Multiply", ReplyAction="*")]
System.Threading.Tasks.Task<int> MultiplyAsync(int intA, int intB);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/Divide", ReplyAction="*")]
System.Threading.Tasks.Task<int> DivideAsync(int intA, int intB);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
public interface CalculatorSoapChannel : CalculatorSoapServiceReference.CalculatorSoap, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
public partial class CalculatorSoapClient : System.ServiceModel.ClientBase<CalculatorSoapServiceReference.CalculatorSoap>, CalculatorSoapServiceReference.CalculatorSoap
{
/// <summary>
/// Implement this partial method to configure the service endpoint.
/// </summary>
/// <param name="serviceEndpoint">The endpoint to configure</param>
/// <param name="clientCredentials">The client credentials</param>
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
public CalculatorSoapClient(EndpointConfiguration endpointConfiguration) :
base(CalculatorSoapClient.GetBindingForEndpoint(endpointConfiguration), CalculatorSoapClient.GetEndpointAddress(endpointConfiguration))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public CalculatorSoapClient(EndpointConfiguration endpointConfiguration, string remoteAddress) :
base(CalculatorSoapClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public CalculatorSoapClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) :
base(CalculatorSoapClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public CalculatorSoapClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public System.Threading.Tasks.Task<int> AddAsync(int intA, int intB)
{
return base.Channel.AddAsync(intA, intB);
}
public System.Threading.Tasks.Task<int> SubtractAsync(int intA, int intB)
{
return base.Channel.SubtractAsync(intA, intB);
}
public System.Threading.Tasks.Task<int> MultiplyAsync(int intA, int intB)
{
return base.Channel.MultiplyAsync(intA, intB);
}
public System.Threading.Tasks.Task<int> DivideAsync(int intA, int intB)
{
return base.Channel.DivideAsync(intA, intB);
}
public virtual System.Threading.Tasks.Task OpenAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndOpen));
}
public virtual System.Threading.Tasks.Task CloseAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginClose(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndClose));
}
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap12))
{
System.ServiceModel.Channels.CustomBinding result = new System.ServiceModel.Channels.CustomBinding();
System.ServiceModel.Channels.TextMessageEncodingBindingElement textBindingElement = new System.ServiceModel.Channels.TextMessageEncodingBindingElement();
textBindingElement.MessageVersion = System.ServiceModel.Channels.MessageVersion.CreateVersion(System.ServiceModel.EnvelopeVersion.Soap12, System.ServiceModel.Channels.AddressingVersion.None);
result.Elements.Add(textBindingElement);
System.ServiceModel.Channels.HttpTransportBindingElement httpBindingElement = new System.ServiceModel.Channels.HttpTransportBindingElement();
httpBindingElement.AllowCookies = true;
httpBindingElement.MaxBufferSize = int.MaxValue;
httpBindingElement.MaxReceivedMessageSize = int.MaxValue;
result.Elements.Add(httpBindingElement);
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap))
{
return new System.ServiceModel.EndpointAddress("http://www.dneonline.com/calculator.asmx");
}
if ((endpointConfiguration == EndpointConfiguration.CalculatorSoap12))
{
return new System.ServiceModel.EndpointAddress("http://www.dneonline.com/calculator.asmx");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
public enum EndpointConfiguration
{
CalculatorSoap,
CalculatorSoap12,
}
}
}
对于第 3 个问题,您可以使用 Message Inspector 扩展。有details here on how to set it up
使用消息检查器,您可以查看、保留和操作关于传出请求、传入回复、传入请求和传出回复的 SOAP 消息。
关于内部工作,WCF 使用 Message 对象来处理 SOAP 信封。你可以阅读更多about that here
消息是通道模型的一部分,相当bit of info here