While working with web apps, you often need to go beyond Visual Studio debugger and see what’s actually being pushed through the wire. This post is a cumulative cheat sheet on how to set up network capturing for various protocols present in .net ecosystem:

  • REST, plain http
  • Entity Framework, plain SQL
  • WCF, regardless of protocol
  • WebServices, SOAP

REST, http

Suppose you’re using a poorly written client library for some REST service and it fails. You can use the following configuration snippet in your web/app.config to capture a lot of diagnostic info and maybe figure out a work-around:

<system.diagnostics>
<sources>
<source name="System.Net" tracemode="protocolonly" maxdatasize="1024">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.Http">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Information"/>
<add name="System.Net.Http" value="Information"/>
</switches>
<sharedListeners>
<add name="System.Net" type="System.Diagnostics.TextWriterTraceListener" initializeData="c:\temp\logs\network.log"/>
</sharedListeners>
<trace autoflush="true"/>
</system.diagnostics>
System.Net Information: 0 : [13844] Current OS installation type is 'Client'.
System.Net Information: 0 : [13844] RAS supported: True
System.Net Information: 0 : [13844] Associating HttpWebRequest#16506093 with ServicePoint#7448163
System.Net Information: 0 : [13844] Associating Connection#51986349 with HttpWebRequest#16506093
System.Net Information: 0 : [13844] Connection#51986349 - Created connection from 10.XXX:8145 to 10.XXX:80.
System.Net Information: 0 : [13844] Associating HttpWebRequest#16506093 with ConnectStream#24590318
System.Net Information: 0 : [13844] HttpWebRequest#16506093 - Request: GET /api/v1/bXXX HTTP/1.1
...
System.Net Error: 0 : [13844] Exception in HttpWebRequest#16506093::GetResponse - The remote server returned an error: (401) Unauthorized..

Entity Framework

Most useful EF single liner ever:

using(var context = new DbContext())
{
    context.Database.Log = x => Diagnostics.Debug.WriteLine(x);
    return context.Table.ToList();
}

WCF

Add the following configuration to start logging entire SOAP/whatever communication between endpoints (works for both client and server):

<system.diagnostics>
<sources>
<source name="System.ServiceModel" switchValue="Information,ActivityTracing"
propagateActivity="true">
<listeners>
<add name="xml" />
</listeners>
</source>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add name="xml" />
</listeners>
</source>
</sources>
<sharedListeners>
<add initializeData="C:\\temp\\TracingAndLogging-service.svclog" type="System.Diagnostics.XmlWriterTraceListener"
name="xml" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>

<system.serviceModel>
<diagnostics wmiProviderEnabled="true">
<messageLogging
logEntireMessage="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true"
maxMessagesToLog="3000"
maxSizeOfMessageToLog="20000"/>
</diagnostics>
</system.serviceModel>

The result file will be large and not exactly human-readable, but there’s Microsoft Windows SDK tool, called SvcTraceViewer.exe which makes it much easier to read. If you have Visual Studio installed, you will probably find this tool in:

C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\

ASMX Webservices

Tracing ASMX communication requires both coding and configuration. You need to add the following class into your project:

using System;
using System.IO;
using System.Web.Services.Protocols;
using System.Xml;

public class SoapTracingExtension : SoapExtension
{
    private string filename;

    private Stream newStream;

    private Stream oldStream;

    public override Stream ChainStream(Stream stream)
    {
        this.oldStream = stream;
        this.newStream = new MemoryStream();
        return this.newStream;
    }

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }

    public override object GetInitializer(Type webServiceType)
    {
        return null;
    }

    public override void Initialize(object initializer)
    {
        this.filename = "c:\\log.txt";
    }

    public override void ProcessMessage(SoapMessage message)
    {
        switch (message.Stage)
        {
            case SoapMessageStage.BeforeSerialize:
            break;
            case SoapMessageStage.AfterSerialize:
            this.WriteOutput(message);
            break;
            case SoapMessageStage.BeforeDeserialize:
            this.WriteInput(message);
            break;
            case SoapMessageStage.AfterDeserialize:
            if (message.Action == "urn:completeOrderWithOrderId")
            {
                message.Exception = new SoapException("Timeout", new XmlQualifiedName("Timeout"));
            }

            break;
            default:
            throw new Exception("invalidstage");
        }
    }

    public void WriteInput(SoapMessage message)
    {
        this.Copy(this.oldStream, this.newStream);
        using (FileStream fs = new FileStream(this.filename, FileMode.Append, FileAccess.Write))
        using (StreamWriter w = new StreamWriter(fs))
        {
            string soapString = (message is SoapServerMessage) ? "SoapRequest" : "SoapResponse";
            w.WriteLine(soapString);
            w.Flush();
            this.newStream.Position = 0;
            this.Copy(this.newStream, fs);
            w.Close();
            this.newStream.Position = 0;
        }
    }

    public void WriteOutput(SoapMessage message)
    {
        this.newStream.Position = 0;
        using (FileStream fs = new FileStream(this.filename, FileMode.Append, FileAccess.Write))
        using (StreamWriter w = new StreamWriter(fs))
        {
            string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";
            w.WriteLine(soapString);
            w.Flush();
            this.Copy(this.newStream, fs);
            w.Close();
            this.newStream.Position = 0;
            this.Copy(this.newStream, this.oldStream);
        }
    }

    private void Copy(Stream from, Stream to)
    {
        using (TextReader reader = new StreamReader(from))
        using (TextWriter writer = new StreamWriter(to))
        {
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }
    }
}

With that code in place, add the following into app/web.config:

<system.web>
<webServices>
<soapExtensionTypes>
<add type="Proper.Namespace.SoapTracingExtension, Proper.Namespace" priority="1" group="0" />
</soapExtensionTypes>
</webServices>
</system.web>