Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Given a HttpResponseMessage, how do I read the content of the request?

Tags:

c#

Given a System.Net.Http.HttpResponseMessage, I can get quite a lot of information about the request I made through response.RequestMessage, for example

response.RequestMessage.RequestUri // the url of the request
response.RequestMessage.Method     // the HTTP method

However, I can't figure out a way to get something useful from

response.RequestMessage.Content    // a StringContent instance

I've looked through the property tree of the StringContent, but I can't figure out how to get its contents as a regular string in a way that works in the Watch Window.

Any suggestions?

like image 306
Tomas Aschan Avatar asked Feb 09 '23 22:02

Tomas Aschan


1 Answers

Analyzing System.Net.Http.dll v2.2.29.0 with ILSpy shows that System.Net.Http.HttpClientHandler.CreateResponseMessage (that initializes an HttpResponse object) doesn't do anything with the corresponding HttpRequest's .Content. That means that if it wasn't null in the first place, the content object itself should still be available.

System.ObjectDisposedException is thrown "when an operation is performed on a disposed object." What does a "disposed object" mean? System.Net.Http.StringContent turns out to implement System.IDisposable through its ancestor, System.Net.Http.HttpContent. So, it actually means "an IDisposable after its .Dispose() method was called."

Naturally, searching for HttpContent.Dispose() users brings us to the culprit - System.Net.Http.HttpClient.SendAsync() that calls System.Net.Http.HttpClient.DisposeRequestContent() after sending the data.


Now, regarding what to do. All that HttpContent.Dispose() does is close the object's streams and set a flag. However, StreamContent (or rather its parent, ByteArrayContent) keeps the data in the .content field - which isn't touched by the disposition!

Alas, both methods that read it directly are protected, and all the public methods that use those check the flag first. So, the only way to read it is with reflection (the illustration is in IronPython, notes are given for C# equivalents):

>>> sc=System.Net.Http.StringContent("abcdefghijklmnopqrstuvwxyz")
#in C#, the following is `type(StringContent)' (this is what it actually does)
>>> scd=System.Reflection.TypeDelegator(System.Net.Http.StringContent)
>>> print scd.GetField("content",System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance)
None     #because the field is in ByteArrayContent and is private
>>> bacd=System.Reflection.TypeDelegator(System.Net.Http.ByteArrayContent)
>>> bacd.GetField("content",System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance)
<System.Reflection.RtFieldInfo object at 0x000000000000002C [Byte[] content]>
# `_' is last result
>>> _.GetValue(sc)
Array[Byte]((<System.Byte object at 0x000000000000002D [97]>, <System.Byte objec
t at 0x000000000000002E [98]>, <System.Byte object at 0x000000000000002F [99]>,
<...>

In C#, this would look like:

type(ByteArrayContent)
    .GetField("content",BindingFlags.NonPublic|BindingFlags.Instance)
    .GetValue(content)
like image 182
ivan_pozdeev Avatar answered Feb 14 '23 10:02

ivan_pozdeev