Accessing Raw SOAP Messages from C#

By Trailblazers Team
Last updated: 12.07.2017
C# SOAP Web Services

The Problem

When working with SOAP web service references in .Net the actual request and response messages are not readily available to the developer.  At times it is useful to be able to access these messages to modify headers or to inspect SOAP Fault responses.  This blog post covers the basics of how to achieve this. 

The pseudo-code that follows is intended as an illustration of the principles that need to be employed to get the SOAP faults, rather than being production-grade code.

 

Our Solution

To demonstrate the solution, consider the scenario of integrating with the HPI Trade SOAP Supplementary Enquiry Service from C#, focusing on how to examine SOAP faults returned from calls made to the service. 

By default, C# does not expose SOAP fault responses.  One way to overcome this is to make use of the SOAP Inspector Behaviours, allowing direct access to both the raw SOAP response and request messages.

 

Firstly, create a new class SoapMessageInspector.cs which implements the System.ServiceModel.Dispatcher.IClientMessageInspector interface:

using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;

namespace Cap-hpi.Api.Data.Behaviours
{
    public class SoapMessageInspector : IClientMessageInspector
    {
        public string LastRequestXml
        {
            get;
            private set;
        }

        public string LastResponseXml
        {
            get;
            private set;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            LastResponseXml = reply.ToString();
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            LastRequestXml = request.ToString();
            return request;
        }
    }
}

 

Then create a new class SoapInspectorBehaviour.cs which implements the System.ServiceModel.Description.IEndpointBehaviour interface:

using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace Cap-hpi.Api.Data.Behaviours
{
    public class SoapInspectorBehaviour : IEndpointBehavior
    {
        private readonly SoapMessageInspector _soapMessageInspector = new SoapMessageInspector();
        public string LastRequestXml => _soapMessageInspector.LastRequestXml;
        public string LastResponseXml => _soapMessageInspector.LastResponseXml;

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(_soapMessageInspector);
        }
    }
}

 

This creates a Client Message Inspector and then applies this through the SOAP Inspector Behaviour. 

 

Next, the SoapInspectorBehaviour needs to be passed in as a behaviour when a call to the service is made, see the following code snippet – points of interest are highlighted in yellow:

public RequestResultsType GetHPICheck(HPICheckParameters parameters)
        {

            var results = new RequestResultsType();
            SoapInspectorBehaviour inspectorBehaviour = new SoapInspectorBehaviour();
            var authentication = new AuthenticationType();
            authentication.SubscriberDetails = new CustomerType()
            {
                CustomerCode = parameters.CustomerCode,
                Password = parameters.Password,
                Initials = parameters.Initials

            };

            var request = new ParameterizedRequestType()
            {
                PrimaryProduct = new ProductType()
                {
                    Code = PrimaryProductCode
                },
            };

            var parameterList = new ParameterType[1];
            request.ParameterList = parameterList;
            request.Asset = new AssetIdentificationType()
            {
                Vrm = parameters.Vrm
            };
            try
            {
                using (var service = new SupplementaryEnquiryV1PortTypeClient())
                {
                    service.Endpoint.Behaviors.Add(inspectorBehaviour);
                    XmlElement[] xmlElements;
                    results = service.parameterizedEnquiry(authentication, request, out xmlElements);
                }
            }
            catch (FaultException ex)
            {
                if (!string.IsNullOrWhiteSpace(inspectorBehaviour.LastResponseXml))
                {
                    ExtractFaultException(inspectorBehaviour);
                }
            }
            return results;
        }

 

Once a SoapInspectorBehaviour object has been created, it needs to be added as an endpoint behaviour to the service to be called.  The service call can be wrapped in a try-catch block and a FaultException can be caught.

 

The fault exception SOAP message can be found in the LastResponseXml property of the inspectorBehaviour object, and the details can be extracted using XPath – an example of the technique is shown here:

        private static void ExtractFaultException(SoapInspectorBehaviour inspectorBehaviour)
        {
            XmlNamespaceManager namespaceManager = new XmlNamespaceManager(new NameTable());
            namespaceManager.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
            namespaceManager.AddNamespace("ns1", "http://webservices.hpi.co.uk/SupplementaryEnquiryV1");

            var errorResponse = XDocument.Load(new StringReader(inspectorBehaviour.LastResponseXml));

            var errorCode = errorResponse
                .XPathSelectElements("//ns1:Error/ns1:Code", namespaceManager)
                .Select(n => n.Value)
                .FirstOrDefault();

            Var errorDescription = errorResponse
                .XPathSelectElements("//ns1:Error/ns1:Description", namespaceManager)
                .Select(n => n.Value)
                .FirstOrDefault();

            Var faultCode = errorResponse
                .XPathSelectElements("//soapenv:Fault/faultcode", namespaceManager)
                .Select(n => n.Value)
                .FirstOrDefault();
            Var faultString = errorResponse
                .XPathSelectElements("//soapenv:Fault/faultstring", namespaceManager)
                .Select(n => n.Value)
                .FirstOrDefault();
	
	    // Do something with the error
        }

 

This technique relies on loading the LastResponseXml (which is a string) into a System.Xml.Linq.XDocument object, and then using XPath to extract the required information.

 

Please note – namespaces are important – the ns1 namespace will change depending on which API call method is being called, please ensure that this value matches the call made otherwise no data will be extracted for errorCode or errorDescription.

 

Conclusion

This method is not just useful for inspecting SOAP Fault messages, it can also be used to get successful responses from the service for further advanced processing.  It is also possible to access and modify the request message before it is sent to the service, by making use of the BeforeSendRequest and AfterReceiveResponse methods of the SoapMessageInspector class.