Why can't I read Http Request Input stream twice?

32,326

Solution 1

StreamReader calls Dispose on given stream when disposed. To leave the stream open use the appropriate constructor for the StreamReader. Or better yet, just copy it to a buffer. From MSDN:

When reading from a Stream, it is more efficient to use a buffer that is the same size as the internal buffer of the stream.

See this question for example.

Solution 2

HttpContext.Current.Request.InputStream.Position=0;

Once you read the position goes to last value, from there its trying to read second time. So before you read, set the position to zero.

Hope it helps.

Share:
32,326

Related videos on Youtube

Michael Levy
Author by

Michael Levy

Been there, done that.

Updated on August 30, 2020

Comments

  • Michael Levy
    Michael Levy over 3 years

    I was putting in some debugging code to test some things, and then the debug code didn't behave as expected. The example below is a simplified code to demonstrate my question.

    This is in .NET 4 and using WebApi, I'm trying to print out the body of the http request in the debug code. To do this I seek the Input stream back and read the stream. It works fine the first time, but if I try to read it again, I get an empty string.

    Why can't I seek back and read the InputStream a second time? In the example below, body2 is always empty. In the second set, CanSeek is still true and the the second call to ReadToEnd() returns an empty string overwriting the default.

    using System.IO;
    using System.Net;
    using System.Net.Http;
    using System.Web;
    using System.Web.Http;
    
    public class TestController : ApiController
    {
    
        public class TestOutuput
        {
            public string firstRead;
            public string secondRead;
        }
    
        public HttpResponseMessage Post()
        {
            string body1 = "default for one";
            string body2 = "default for two";
            if (HttpContext.Current.Request.InputStream.CanSeek)
            {
                HttpContext.Current.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);
            }
            using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
            {
                body1 = reader.ReadToEnd();
            }
    
            if (HttpContext.Current.Request.InputStream.CanSeek)
            {
                HttpContext.Current.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);
            }
            using (var reader2 = new StreamReader(HttpContext.Current.Request.InputStream))
            {
                // this is always empty, even after seek back to origin
                body2 = reader2.ReadToEnd();
            }
    
            TestOutuput testOutput = new TestOutuput() { firstRead = body1, secondRead = body2 };
            HttpResponseMessage response = new HttpResponseMessage();
            return Request.CreateResponse<TestOutuput>(HttpStatusCode.OK, testOutput);
        }
    }
    
    • Jon Skeet
      Jon Skeet about 10 years
      Well does CanSeek return true? You're currently conditionally seeking, but then assuming that the seek has worked. Why are you even doing this? I would personally expect that at least for large request bodies the stream may not be buffered in memory. If you want to create a copy, I suggest you do that first and then read multiple times from your copy. Use Stream.CopyTo in conjunction with a MemoryStream.
    • Michael Levy
      Michael Levy about 10 years
      Yes, CanSeek does return true and the ReadToEnd is executed. Good, point. I'll update the question to clarify.
    • John Saunders
      John Saunders about 10 years
      Have you tried Fiddler?
    • Peter Ritchie
      Peter Ritchie about 10 years
      You can copy the stream to a MemoryStream as Jon pointed out, but the Stream interface in a web request is really just an abstraction of the TCP stream that doesn't support all to possible TCP operations you could perform on that socket (i.e. you could request that the packet be sent again--but you rarely ever want to do that if you simply want to re-read the data).
    • Michael Levy
      Michael Levy about 10 years
      @JonSkeet - All good advice, thanks.
  • Michael Levy
    Michael Levy about 10 years
    Mystery solved. Thank you. Apparently, the constructors for StreamReader with the leaveOpen parameter are only available in .net 4.5, and I'm using 4.0. I unwrapped the Using statement into a try/catch/finally and then I was able to not close the first StreamReader and sure enough, the second pass works. This was just some throw away code I had put in to debug something else, but the mysterious behavior stumped me. Thanks for the explanation.
  • Michael Levy
    Michael Levy about 10 years
    And just to summarize the best info from this answer and comments above: If you want to read the stream twice, don't use a StreamReader directly against the underlying stream because when the StreamReader is closed, it closes the underlying stream. Make a MemoryStream copy first, and then read from that.
  • Taran
    Taran over 8 years
    stream.Seek(0, SeekOrigin.Begin);
  • peco
    peco over 8 years
    Any idea on how to do this in a self hosted scenario without HttpContext.Current?
  • Mike Flynn
    Mike Flynn almost 5 years
    This doesnt work, the stream is empty because of the using statment
  • nbushnell
    nbushnell almost 3 years
    remove the using and this will work fine.