如何记录 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