1

I'm having some trouble with finding the right incantation that will allow me to write to a response stream and then later read the contents in a test. Currently I have this

var res = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var ms = new MemoryStream();
res.Content = new StreamContent(ms);
using (var sw = new StreamWriter(ms, System.Text.Encoding.UTF8))
using (var csv = new CsvHelper.CsvWriter(sw))
  csv.WriteRecords(allData.ToList());
return res;

In my test I'm trying to read this response

  var controller = appContainer().Resolve<MyController>();
  var res = (await controller.Get()) as HttpResponseMessage;
  res.ShouldNotEqual(null);
  var csv = await res.Content.ReadAsStringAsync();

the last line generates an error

Error while copying content to a stream.
  ----> System.ObjectDisposedException : Cannot access a closed Stream.

So there's a couple things here

  • Why is this error happening and how can I prevent it properly in the test?
  • The use of MemoryStream doesn't sit right with me, shouldn't I be able to write directly to the content's stream? Isn't MemoryStream potentially hugely increasing my memory usage?
George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • The `using` statements are causing the stream to be disposed. – Nkosi Mar 29 '17 at 19:01
  • No not doubling memory usage. the content's stream is the one you assigned to it. check source code – Nkosi Mar 29 '17 at 19:03
  • @Nkosi so let's say I'm writing 10MB of CSV. I will need a 10MB chunk of memory to buffer these. But hold on! I can start sending back parts of the body without having the entire csv rendered into memory, therefore I wouldn't *actually* need the whole 10MB all at once, but more of a smaller moving window. This is what you do with `FileStreamResult` and I'd like to get a similar efficiency here. – George Mauer Mar 29 '17 at 20:18
  • @GeorgeMauer Did you ever find a solution to this? You made similar comments in this [question](https://stackoverflow.com/questions/29948809/web-api-return-csv-file) but there's no solution posted. – Nse Sep 28 '17 at 18:08
  • @Nse just checked what I ended up doing and I actually did go with the memory stream. I'm not so sure this is an asp.net web api issue anymore however - this might be a `CsvHelper` bug as disposing the `CsvWriter` closes the underlying stream. A future version will have an option not to do that, but I've looked at the code and the version that I'm using (2.16.3.0) its actually fine to leave undisposed, flush the streamwriter, and reset the memorystream position – George Mauer Sep 29 '17 at 02:24

1 Answers1

2

Just put this out there, though it's not perfect... Using PushStreamContent does a lot of the job but it comes with its own headaches - namely that any exceptions that your anonymous method may produce will get swallowed and be difficult to track down without a full repro of the problem. When the bomb goes off is well passed the point of the pipeline where web api unhandled exception handlers would come into effect, and the xmlhttprequest doesn't seem to recognize the closure.

E.g. something like

HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new PushStreamContent((stream, content, context) =>
{
     // write your output here
});
return response;

will get you what you want, provided that internal method never slips up or goes wrong in any way.

PushStreamContent flushes your http headers immediately before the anonymous method is called, so you're chunked and no way to reel it back in later.

You can add a try/catch in your anonymous method to leave yourself a note if something goes wrong, but in my experience XmlHttpRequest doesn't recognize when the remote server forcibly closes the request so it keeps on waiting. Only started to figure out what was going on when I put Fiddler in there, and Fiddler squawked.

user1664043
  • 695
  • 5
  • 14