Search code examples
c#visual-studioweb-serviceswcfsoap

How do I log SOAP requests?


As most of us already know, in Visual Studio 2017, you can add a Connected Service and configure it to point to the SOAP service you'd like, using the relevant WSDL. That creates a set of proxy classes in a Reference.cs file that are used to interface with the SOAP service in question.

Is there a way to intercept the converted SOAP at some point in the process - programmatically - so we can know what exactly is being sent to the target service? I ask this because I need to be able to log the actual SOAP that's being sent out, so using a separate application such as Fiddler is not an option.

I have pored through the code in the Reference.cs file and everything is so abstracted away that I can't tell where the conversion actually happens.

For the sake of this discussion, I am using a very simple free SOAP service located at: http://www.dneonline.com/calculator.asmx

The contents of the generated Reference.cs file are shown at the end of this post. In that code, you can see that the client class's method AddAsync(...), for example, calls base.Channel.AddAsync(intA, intB), a method which is defined in the CalculatorSoap interface. So, if Channel is a CalculatorSoap interface, where is the concrete class that is actually being used? This is an example of what I mean by the prevalence of so much abstraction.

I'll be happy (and grateful) to hear any ideas from any of you regarding what is happening under the hood here.

Thank you.

//------------------------------------------------------------------------------
// <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,
        }
    }
}

Solution

  • For your question number 3, you can use the Message Inspector extension. There are details here on how to set it up

    With the message inspector you can view, persist, manipulate the SOAP message on out going requests, incoming replies, incoming requests, and outgoing replies.

    Also regarding the inner workings WCF uses the Message object for processing SOAP envelopes. You can read more about that here

    The message is part of the Channel Model, there is quite a bit of info here