18

I'm trying to implement this code example, but get a HttpRequestException - "Error while copying content to a stream." when the ReadAsStringAsync() method is called. The inner exception is "Cannot access a disposed object." I'm using Fiddler to make the request. I don't understand. Can someone explain why I'm getting this exception and offer a solution?

Web Api Method:

public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
{
    try
    {
        var jsonString = await request.Content.ReadAsStringAsync();
    }
    catch (Exception ex)
    {                
        throw;
    }
    return new HttpResponseMessage(HttpStatusCode.Created);
}

Fiddler (POST):

User-Agent: Fiddler
Host: localhost:23567
Content-Length: 18
Content-Type: application/json; charset=utf-8
Body{"Test":1}

Edit:

I have a clue, but need verification. On the Web Api controller, I have an ActionFilterAttribute and in its OnActionExecuting override, there's this line:

public override async void OnActionExecuting(HttpActionContext actionContext)
{
    // omitted code
    actionContext.Request.Content.ReadAsStreamAsync();
}

Could it be that because the Content is read here, it's not available again? If so, how can I make it available in the method? Is the Content here the same as the HttpRequestMessage? This may contain an answer.

Community
  • 1
  • 1
Big Daddy
  • 5,160
  • 5
  • 46
  • 76

5 Answers5

8

Just a guess, should post as comment but I want include a code snippet:

Maybe you call Post function inside a using block, but don't use await.

using (HttpRequestMessage request = ...)
{
    // Maybe you use this:
    Post(request);

    // Instead of this
    var response = await Post(request);
}

Or you don't dispose old connects properly.

Also, try add HttpVersion.Version10 to your request, which change header request from Connection: keep-alive to Connection: close, which can cause exception in some case you reuse a host (Search for more info)

request.Version = HttpVersion.Version10;
var jsonString = await request.Content.ReadAsStringAsync();
Michael Meadows
  • 27,796
  • 4
  • 47
  • 63
NoName
  • 7,940
  • 13
  • 56
  • 108
3

I resolved with this, my problem was that the response was in gzip:

var handler = new HttpClientHandler();
        if (handler.SupportsAutomaticDecompression)
        {
            handler.AutomaticDecompression = DecompressionMethods.GZip |
                                             DecompressionMethods.Deflate;
        }
        client = new HttpClient(handler);

var content = new FormUrlEncodedContent(valoresPost);
var response = await client.PostAsync(url, content);
        
                var contenidoPdf = await response.Content.ReadAsByteArrayAsync();
             
2

Because the controller's ActionFilterAttribute's OnActionExecuting method is calling ReadAsStreamAsync, the Content can't be read again. I changed ReadAsStreamAsync to ReadAsStringAsync and the request's Content is available in the controller. Apparantly, ReadAsStringAsync buffers the Content so it's still available. This link provided the answer.

Community
  • 1
  • 1
Big Daddy
  • 5,160
  • 5
  • 46
  • 76
2

I hope this (late) post will help someone someday...

In short: the accepted answer suggests to read the entire file as string (and not as stream) in order to bypass a read problem

BUT... reading a file as a string is not such a great idea

I figured out that replacing MultipartFormDataStreamProvider with MultipartMemoryStreamProvider works great - and let you read your uploaded file as desired

My code (at least the relevant parts of it)

    [HttpPost]
    [Route("upload/file")] // you may replace this route to suit your api service
    public async Task<IHttpActionResult> Upload()
    {
        if (!Request.Content.IsMimeMultipartContent("form-data"))
        {
            return BadRequest("Unsupported media type");
        }

        try
        {
            var provider = new MultipartMemoryStreamProvider();

            await Request.Content.ReadAsMultipartAsync(provider);

            if (provider.Contents.Count == 0) return InternalServerError(new Exception("Upload failed"));

            var file = provider.Contents[0]; // if you handle more then 1 file you can loop provider.Contents

            var buffer = await file.ReadAsByteArrayAsync();

            // .. do whatever needed here

            return Ok();

        }
        catch (Exception ex)
        {
            return BadRequest(ex.GetBaseException().Message);
        }
    }
ymz
  • 6,602
  • 1
  • 20
  • 39
0

I am adding to the conversation, as I encountered this error in a different context. For me I had an existing call from my web app to a separate API. It was previously working and then stopped and I was getting this error and couldn't track down the reason why. What it turned out to be was a circular reference in my entity framework, essentially navigation property between two objects that shouldn't have been there. It wasn't breaking the API as I assume EF has a default configuration for the depth that navigation properties will be populating to prevent a stack overflow, but it must have been overwhelming the stream on the receiving end and causing the connection to time out.

cigien
  • 57,834
  • 11
  • 73
  • 112