Question

I have a loop that is iterating through a short list of URIs and needs to send each of them the same content. That HttpContent resides in a variable passed into the call to PostAsync. The problem I'm incurring is that the 1st call works perfectly, but all subsequent calls fail because the request content is null after the 1st call completes.

I would assume my understanding of the PostAsync call under the covers might be lacking some low level detail on the object and it's lifetime (or lack there of) so hopefully someone can shed some light on it for me.

The code looks as follows:

HttpResponseMessage httpResponseMessage = httpClient.PostAsync(destionationUri, messageContent).Result;

The variable in question is messageContent which is of type MultipartFormDataContent which derives from HttpContent. I suppose some brute method of copying the value to a new and separate location for reuse might work, but I'm looking to understand why this happening and a proper method for allowing the content to persist during the loop.

Does anyone know how I can fix this issue, and upon looping around the code above, the messageContent value will persist between iterations?

EDIT: I went ahead and refactored the code to rebuild the MultipartFormDataContent from scratch using the same data prior to each call to PostAsync using a new instance of HttpClient. The call to rebuild the HtppContent actually worked; the value is not null. However when I now go to make a subsequent call to PostAsync and access .Result I get the following error:

Error while copying content to a stream. Cannot access a closed Stream.

EDIT 2: Upon inspecting the 2nd call to my method to rebuild the MultipartFormDataContent object from scratch, I notice that my attachment property when inspecting it's ContentStream property (of a Mail.MailMessage object) states the following:

attachment.ContentStream.Position threw an exception of type System.ObjectDisposedException

All of the properties indicate the ContentStream can no longer be read because it's disposed. The call to PostAsync().Result apparently is disposing of the stream and is potentially the root issue. However I'm still unable to find a workable solution.

Was it helpful?

Solution

You can make the solution a bit more tidy by creating a new "wrapper" HttpContent class that allows your content class to be re-used. When sending content, HttpClient automatically disposes any request content, "just in case". This isn't idea for your case. Fortunately, the Dispose(bool) method is virtual, so you can override the dispose behaviour in derived class.

 public class ReusableContent : HttpContent
    {
        private readonly HttpContent _innerContent;

        public ReusableContent(HttpContent innerContent)
        {
            _innerContent = innerContent;
        }

        protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            await _innerContent.CopyToAsync(stream);
        }

        protected override bool TryComputeLength(out long length)
        {
            length = -1;
            return false;
        }

        protected override void Dispose(bool disposing)
        {
            // Don't call base dispose
            //base.Dispose(disposing);
        }
    }

Once you have this class, you can use it like this,

    [Fact]
    public async Task Sending_same_content_multiple_times()
    {
        var client = new HttpClient { BaseAddress = _BaseAddress };


        var stream = new MemoryStream();
        var sw = new StreamWriter(stream);
        sw.Write("This is a stream");
        sw.Flush();
        stream.Position = 0;

        var multipart = new MultipartContent
        {
            new StringContent("This string will be sent repeatedly"),
            new FormUrlEncodedContent(new[] {new KeyValuePair<string, string>("foo", "bar"),}),
            new StreamContent(stream)
        };

        var content = new ReusableContent(multipart);

        for (int i = 0; i < 10; i++)
        {
            var response = await client.PostAsync("devnull", content);
            response.EnsureSuccessStatusCode();
        }

    }

If you do use this, make sure you don't use an content that does actually need to be disposed without disposing of it manually.

OTHER TIPS

I have found a solution and it works but is not really the answer I was looking to find. The ContentStream as I mentioned once read by the PostAsync().Result() was closed/disposed of automatically. Not the behavior I wanted. Even upon trying to rebuild the MultipartFormDataContent with the attachment again it was not successful.

This post: https://stackoverflow.com/a/8100653/410937 got me thinking to go even further up the chain to where the attachments are initially loaded into a MemoryStream and re-run that method. The result was a working solution. I essentially have to rebuild the entire request object completely from scratch including all native and raw elements, and then POST again.

Seems to much to me as I just wanted to loop and POST to 1..n URIs using the same content. All the nuances made me walk all the way back up the line to rebuild the request content from scratch to make it work.

The solution if existed I was really seeking was more along the following:

  1. Notify PostAsync().Result() to not close my stream after reading
  2. Resurrect the file attachment stream to be readable again without having to recreate the entire request. Although since it was disposed this was probably not feasible.

I'm open to a more direct solution but this does actually work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top