172

I have a method that does a POST like below

var response = await client.PostAsJsonAsync(url, entity);

if (response.IsSuccessStatusCode)
{
        // read the response as strongly typed object
        return await response.Content.ReadAsAsync<T>();
}

My question is how can I obtain the actual JSON that got posted from the entity object. I would like to log the JSON that gets POSTED, so it will be nice to have that without me having to do a json serialize myself.

Kiran
  • 56,921
  • 15
  • 176
  • 161
govin
  • 6,445
  • 5
  • 43
  • 56

4 Answers4

298

An example of how you could do this:

Some notes:

  • LoggingHandler intercepts the request before it handles it to HttpClientHandler which finally writes to the wire.

  • PostAsJsonAsync extension internally creates an ObjectContent and when ReadAsStringAsync() is called in the LoggingHandler, it causes the formatter inside ObjectContent to serialize the object and that's the reason you are seeing the content in json.

Logging handler:

public class LoggingHandler : DelegatingHandler
{
    public LoggingHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    {
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Console.WriteLine("Request:");
        Console.WriteLine(request.ToString());
        if (request.Content != null)
        {
            Console.WriteLine(await request.Content.ReadAsStringAsync());
        }
        Console.WriteLine();

        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        Console.WriteLine("Response:");
        Console.WriteLine(response.ToString());
        if (response.Content != null)
        {
            Console.WriteLine(await response.Content.ReadAsStringAsync());
        }
        Console.WriteLine();

        return response;
    }
}

Chain the above LoggingHandler with HttpClient:

HttpClient client = new HttpClient(new LoggingHandler(new HttpClientHandler()));
HttpResponseMessage response = client.PostAsJsonAsync(baseAddress + "/api/values", "Hello, World!").Result;

Output:

Request:
Method: POST, RequestUri: 'http://kirandesktop:9095/api/values', Version: 1.1, Content: System.Net.Http.ObjectContent`1[
[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Headers:
{
  Content-Type: application/json; charset=utf-8
}
"Hello, World!"

Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Date: Fri, 20 Sep 2013 20:21:26 GMT
  Server: Microsoft-HTTPAPI/2.0
  Content-Length: 15
  Content-Type: application/json; charset=utf-8
}
"Hello, World!"
Kiran
  • 56,921
  • 15
  • 176
  • 161
  • 6
    That is nice if you need the request details but it fail at getting the exact request sent to the server. If you need precisely all the byte sent to the server it not gonna work this way. – mathk Nov 10 '14 at 10:25
  • .NET adds `Expect: 100-continue` to the headers of POST requests, and this method will not show that. To get rid of that particular header, do this: `client.DefaultRequestHeaders.ExpectContinue = false;` – Brian Oliver Mar 16 '16 at 13:44
  • @kiran-challa: You might want to add .ConfigureAwait(false) to the await-lines to prevent possible deadlocks: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx?f=255&MSPPError=-2147217396 – Lars335 Oct 20 '16 at 20:17
  • 1
    Why the `new HttpClientHandler()`? It is not present in the official docs: https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/httpclient-message-handlers – Zero3 Jun 15 '17 at 22:15
  • 1
    Ah, it is apparently required to not get an exception about the inner handler being null... – Zero3 Jun 16 '17 at 00:21
  • 6
    You can also override [MessageProcessingHandler](https://msdn.microsoft.com/en-us/library/system.net.http.messageprocessinghandler(v=vs.118).aspx) which basically calls a `ProcessRequest` and `ProcessResponse` method for you before and after the `SendAsync` call. – IllusiveBrian Feb 08 '18 at 19:41
  • 2
    @RamiA's answer below is better, because it doesn't require code changes. Once you're done debugging, you remove the tracing from your config, and that's it. No need to make a new build. – Tsahi Asher Feb 20 '18 at 09:42
  • 1
    @TsahiAsher It's not better. It's just different. What the preferable solution is depends on the use case. For instance it will often be a key requirement of an enterprise application which integrates with an external third party that every engagement with that third party is audited and persisted. Dumping to trace files is not necessarily a sustainable solution. – Dave Lawrence Mar 14 '22 at 17:05
  • @DaveLawrence you can dump it to anything. There are several listeners built in, and you can write your own or use a third party listener which will send it to a logging framework. The possibilities are endless. – Tsahi Asher Mar 15 '22 at 15:50
  • 1
    For anyone wondering - you can also use it with typed client and `HttpClientFactory`: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0#outgoing-request-middleware – Mr Patience Oct 14 '22 at 17:30
69

NOTE: This works with .NET Framework ONLY!

May be working with .NET 7+ (https://github.com/dotnet/runtime/issues/23937)


See http://mikehadlow.blogspot.com/2012/07/tracing-systemnet-to-debug-http-clients.html

To configure a System.Net listener to output to both the console and a log file, add the following to your assembly configuration file:

<system.diagnostics>
  <trace autoflush="true" />
  <sources>
    <source name="System.Net">
      <listeners>
        <add name="MyTraceFile"/>
        <add name="MyConsole"/>
      </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add
      name="MyTraceFile"
      type="System.Diagnostics.TextWriterTraceListener"
      initializeData="System.Net.trace.log" />
    <add name="MyConsole" type="System.Diagnostics.ConsoleTraceListener" />
  </sharedListeners>
  <switches>
    <add name="System.Net" value="Verbose" />
  </switches>
</system.diagnostics>
SerjG
  • 3,325
  • 3
  • 30
  • 30
Rami A.
  • 10,302
  • 4
  • 44
  • 87
  • 3
    How does it work ? I copied `` into `app.config` under `` but in `bin` folder there is no log file and console output don't display anything too, what am I missing ? – Muflix Jun 10 '20 at 12:43
  • 1
    @Muflix, you can read the documentation which is linked in the page referenced at the top of my answer. I think the file name specified in the `initializeData` attribute will be created in the current working directory of the executable you are running, so you may want to check which path that is in your environment. – Rami A. Jun 11 '20 at 02:05
  • 1
    @Rami A. Nobody would seriously call the linked blog post "documentation". It doesn't explain more then your answer. For me it's not working but maybe for a plugin dll I have to do something more... or just write the interesting stuff to file by myself like Kiran Challa does. – The incredible Jan Jul 05 '21 at 14:49
  • @TheincredibleJan there's a link within the blogpost: https://learn.microsoft.com/en-us/dotnet/framework/network-programming/how-to-configure-network-tracing You can contribute to the documentation by providing your feedback at the bottom of the page. It's the official documentation and contains more technical information than my answer. Why don't you think it's "serious" documentation? – Rami A. Jul 05 '21 at 20:18
  • 2
    This answer is completely WRONG because it DOES NOT WORK for `HttpClient`. – Ian Kemp Aug 04 '22 at 09:27
  • @IanKemp HttpClient is in the System.Net namespace in the .NET Framework, so it should work. This probably doesn't work if you are using .NET Standard, .NET Core, or .NET 5+. This answer was added before .NET Core was released. – Rami A. Aug 04 '22 at 18:35
  • 1
    It might work now in .NET 7 if you use the new API described towards the end here to configure the tracing from the config file: https://github.com/dotnet/runtime/issues/23937 – Mark Sowul Nov 22 '22 at 19:48
24

Network tracing also available for next objects (see article on msdn)

  • System.Net.Sockets Some public methods of the Socket, TcpListener, TcpClient, and Dns classes
  • System.Net Some public methods of the HttpWebRequest, HttpWebResponse, FtpWebRequest, and FtpWebResponse classes, and SSL debug information (invalid certificates, missing issuers list, and client certificate errors.)
  • System.Net.HttpListener Some public methods of the HttpListener, HttpListenerRequest, and HttpListenerResponse classes.
  • System.Net.Cache Some private and internal methods in System.Net.Cache.
  • System.Net.Http Some public methods of the HttpClient, DelegatingHandler, HttpClientHandler, HttpMessageHandler, MessageProcessingHandler, and WebRequestHandler classes.
  • System.Net.WebSockets.WebSocket Some public methods of the ClientWebSocket and WebSocket classes.

Put the following lines of code to the configuration file:

<configuration>  
  <system.diagnostics>  
    <sources>  
      <source name="System.Net" tracemode="includehex" maxdatasize="1024">  
        <listeners>  
          <add name="System.Net"/>  
        </listeners>  
      </source>  
      <source name="System.Net.Cache">  
        <listeners>  
          <add name="System.Net"/>  
        </listeners>  
      </source>  
      <source name="System.Net.Http">  
        <listeners>  
          <add name="System.Net"/>  
        </listeners>  
      </source>  
      <source name="System.Net.Sockets">  
        <listeners>  
          <add name="System.Net"/>  
        </listeners>  
      </source>  
      <source name="System.Net.WebSockets">  
        <listeners>  
          <add name="System.Net"/>  
        </listeners>  
      </source>  
    </sources>  
    <switches>  
      <add name="System.Net" value="Verbose"/>  
      <add name="System.Net.Cache" value="Verbose"/>  
      <add name="System.Net.Http" value="Verbose"/>  
      <add name="System.Net.Sockets" value="Verbose"/>  
      <add name="System.Net.WebSockets" value="Verbose"/>  
    </switches>  
    <sharedListeners>  
      <add name="System.Net"  
        type="System.Diagnostics.TextWriterTraceListener"  
        initializeData="network.log"  
      />  
    </sharedListeners>  
    <trace autoflush="true"/>  
  </system.diagnostics>  
</configuration>  
Pang
  • 9,564
  • 146
  • 81
  • 122
StuS
  • 817
  • 9
  • 14
-28

The easiest solution would be to use Wireshark and trace the HTTP tcp flow.