29

I have a client that communicates with a web service. The class that I communicate with is a C# class that is generated through wsdl.exe. I now want to log all incoming and outgoing messages.

What I've done so far is to write a class that inherits from the automatically generated C# Class and I have overridden the GetReaderForMessage method. That way I can access the incoming message more or less like this:

protected override XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize)
{
    System.Xml.XmlReader aReader = base.GetReaderForMessage(message, bufferSize);
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.Load(aReader);
    string content = doc.InnerXml.ToString();
    System.Xml.XmlReader aReader2 = System.Xml.XmlReader.Create(new System.IO.StringReader(content));

    return aReader2;
}

Obviously I'm not too happy with this solution, because basically I'm creating two xml readers. One to read the contents of the SOAP message and one to return to the method caller. Plus I can't really do the same with the GetWriterForMessage method.

But may be I'm just doing things too difficult to start with. Is it for instance possible to read the contents of the SoapClientMessage object directly? I've read some articles suggesting that I should use SoapExtensions here, but from what I can understand, that would only work if the 'client' that I am creating is itself a web service which in this case it is not.

Any suggestions?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
trabart
  • 377
  • 1
  • 3
  • 6
  • Is there a reason you're not using WCF? – John Saunders Oct 07 '10 at 09:51
  • 7
    I can answer your question @JohnSaunders . I'm working with a huge base of legacy code that 'works' and the business doesn't want it to 'not work' thus any change to 'working' code is frowned upon. – Chris Hayes Oct 16 '14 at 20:48
  • @ChrisHayes: are you @trabart? If not, then how can you answer the question I asked him. BTW, I agree with "if it ain't broke, don't fix it". But to my mind, the need to modify existing code (by adding logging, for instance), means that the code is "broke". In particular, it's _so_ much easier to do what the OP wants to do using WCF than with ASMX. It's worth considering the upgrade if that code will continue to need changes. Similarly, VB6 code that needs frequent changes should be upgraded to .NET since it will make the changes much easier. – John Saunders Oct 16 '14 at 20:51
  • Trabart, I have similar project boundaries - service is WCF service with basicHttpBinding, but client is limited to use .Net Framework 2.0. I found load of WCF client/server solutions, but not for that mixed one. Finally I've used solution described in [I am consuming a WCF service that requires headers from a .NET 2 website. How can I programmatically add the headers to the messages?](https://stackoverflow.com/questions/16276491/i-am-consuming-a-wcf-service-that-requires-headers-from-a-net-2-website-how-ca) article by @Ceottaki. Hope it helps. – Rudolf Dvoracek Jun 24 '15 at 09:49

2 Answers2

61

You need to use "Add Service Reference" and not "Add Web Reference" feature to use this solution, it can be used if the service is ASMX or WCF. (You need to use .NET Framework 3.X to use this feature)

This article will help you to add the service reference to your C# project.

To intercept and XMLs of the request and the response, Implement these two classes:

public class InspectorBehavior : IEndpointBehavior
{
    public string LastRequestXML { 
        get
        {
            return myMessageInspector.LastRequestXML;
        }
    }

    public string LastResponseXML { 
        get
        {
            return myMessageInspector.LastResponseXML;
        }
    }


    private MyMessageInspector myMessageInspector = new MyMessageInspector();
    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(myMessageInspector );
    }
}





public class MyMessageInspector : IClientMessageInspector
{
    public string LastRequestXML { get; private set; }
    public string LastResponseXML { get; private set; }
    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
        LastResponseXML = reply.ToString();
    }

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
    {
        LastRequestXML = request.ToString();
        return request;
    }
}

Then, change the call code to:

MyTestServiceSoapClient client = new MyTestServiceSoapClient();
var requestInterceptor = new InspectorBehavior();
client.Endpoint.Behaviors.Add(requestInterceptor );
client.DoSomething("param1", "param2", "param3");
string requestXML = requestInterceptor.LastRequestXML;
string responseXML = requestInterceptor.LastResponseXML;

****EDIT**** This is not related with the serverside technology, you can use it with WCF, ASMX, PHP, ... web services, I have tested on: http://www.w3schools.com/webservices/tempconvert.asmx

And got the following XMLs:

requestXML=

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/CelsiusToFahrenheit</Action>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <CelsiusToFahrenheit xmlns="http://tempuri.org/">
      <Celsius>50</Celsius>
    </CelsiusToFahrenheit>
  </s:Body>
</s:Envelope>

responseXML=

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" />
  <soap:Body>
    <CelsiusToFahrenheitResponse xmlns="http://tempuri.org/">
      <CelsiusToFahrenheitResult>122</CelsiusToFahrenheitResult>
    </CelsiusToFahrenheitResponse>
  </soap:Body>
</soap:Envelope>

****EDIT 2****

"Add Web Reference" is not specialized for ASMX and is not an ASMX client-side technology, and "Add Service Reference"is not the WCF client-side technology, you can use both to add reference to ASMX, WCF, JSP-developed or PHP-developed, web service, you need your application to use .Net framework 3.5 to use "Add Service Reference".

This article mentions:

When using the Add Web Reference dialog box in Visual Studio, a client proxy is generated using WSDL information and is added to the Visual Studio project. This is usually used for ASMX services, but you can also use the Add Web Reference dialog box to create a client proxy for WCF services. However, you need to manually type the service URL, and the proxy that is generated uses XML serialization, which is the only type of serialization supported. To create client proxies for WCF services that support the data contract serializer, you can use the Svcutil.exe tool or use the Add Service Reference feature of the Visual Studio Development Tools for the .NET Framework 3.x.

Saw
  • 6,199
  • 11
  • 53
  • 104
  • 2
    He appears to be using "Add Web Reference", the ASMX client-side technology, not "Add Service Reference", which is the WCF client-side technology. – John Saunders Dec 09 '12 at 05:48
  • 1
    You're mistaken. WCF includes client side code and server-side code. "Add Service Reference" creates a WCF client for whatever kind of service. – John Saunders Dec 09 '12 at 15:07
  • But he can change the way of generating the proxy class and use svcutil.exe instead of wsdl.exe, and generate a WCF client as you said!! he can do this easily even if his service not a WCF service, I have mentioned this in my solution. – Saw Dec 09 '12 at 15:47
  • Perfect! Thank you! To get the very details from a Java Web Service SoapFault Exception from a C# client was giving me a hard time, using this technique I could solve the problem. Cheers! ;-) – Ualter Jr. Oct 22 '16 at 18:55
  • The interceptor works well but better put it in a Try catch: try { var response = await myclient.doactionAsync(); return response; } catch { _logger.LogInformation(myclient.requestInterceptor.LastRequestXML); _logger.LogInformation(myclient.requestInterceptor.LastResponseXML); } – Claudio Ferraro Jul 07 '23 at 02:17
6

I would suggest looking into using a SOAP extension, which in my opinion is ideal for this scenario. Here are a few links that describe the process.

http://msdn.microsoft.com/en-us/magazine/cc164007.aspx

https://ebay.custhelp.com/cgi-bin/ebay.cfg/php/enduser/std_adp.php?p_faqid=350

http://www.codeproject.com/KB/webservices/efficientsoapextension.aspx

Garett
  • 16,632
  • 5
  • 55
  • 63
  • OK... One more problem. I now have a class that inherits from the class that was generated by wsdl. This class has a Login method that calls the Login method from the 'wsdl'-class. When I apply the extension to this wsdl-class, everything seems to work, but I don't want this, because this class could be regenerated when the web service changes. So now I want to apply this extension to my own class, but then the methods in the extension class are not being called... Any suggestions? – trabart Oct 07 '10 at 07:51