0

I have multiple Attribute classes which need to access the data in an HttpContent (which is in an HttpActionContext's HttpRequstMessage) but I am having issues deciding on the best way to do it. I considered using the ReadAsStringAsync method on the HttpContent, but it seems that I need access to the Stream to first set the position to 0.

The naive way seems to just wrap a StreamReader around the base stream with a using statement like so:

public override void OnActionExecuting(HttpActionContext actionContext)
{
  string rawJson;
  using (StreamReader streamReader = new StreamReader(actionContext.Request.Content.ReadAsStreamAsync().Result))
  {
    streamReader.BaseStream.Position = 0;
    rawJson = streamReader.ReadToEnd();
  }
  // etc
}

This works well for the first attribute that executes but every subsequent attribute that executes throws an exception on actionContext.Request.Content.ReadAsStreamAsync() because it was disposed.

Here are alternative methods that work, but I'd really like to know the optimal method of doing this.

1: Wrap a StreamReader around the base stream and never dispose it.

public override void OnActionExecuting(HttpActionContext actionContext)
{
  StreamReader streamReader = new StreamReader(actionContext.Request.Content.ReadAsStreamAsync().Result);
  streamReader.BaseStream.Position = 0;
  string rawJson = streamReader.ReadToEnd();     
  // etc
}

This works well for every attribute but I worry that I might be leaking memory by not disposing the StreamReader.

2: Copy the base stream to a MemoryStream and wrap that with a StreamReader.

public override void OnActionExecuting(HttpActionContext actionContext)
{
    MemoryStream memoryStream = new MemoryStream();
    Stream s = actionContext.Request.Content.ReadAsStreamAsync().Result;
    s.Position = 0;
    s.CopyTo(memoryStream);
    string rawJson;
    using (StreamReader streamReader = new StreamReader(memoryStream))
    {
      streamReader.BaseStream.Position = 0;
      rawJson = streamReader.ReadToEnd();
    }
  // etc
}

This works well for every attribute but is a lot of code and might be inefficient.

3: Set the base stream's position to 0 then call ReadAsStringAsync.

public override void OnActionExecuting(HttpActionContext actionContext)
{
  actionContext.Request.Content.ReadAsStreamAsync().Result.Position = 0;
  string rawJson = actionContext.Request.Content.ReadAsStringAsync().Result;
  // etc
}

This works well for every attribute but might be less efficient.

So, is there an optimal way to do this without leaking memory?

Community
  • 1
  • 1
Aaron T
  • 1,092
  • 2
  • 10
  • 25

1 Answers1

0

Use the stream reader that leaves the stream open - because you don't take ownership of the stream, it is still owned in the scope of the HTTP action content.

public StreamReader (System.IO.Stream stream, System.Text.Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen);

Constructor with the option to leave the stream open

Also use of .Result to complete asynchronous calls in a sync context is risky due to possible deadlocks. Try to refactor your methods as asynchronous instead.

David Burg
  • 1,064
  • 12
  • 14