2

In our ASP.NET Core 1.1, EF Core 1.1 app we have populated a string with some data from SQL Server and would like to have a text file created out of it on the fly and have a user save/download from the client side.

In old days we used to do it as follows:

List<string> stringList = GetListOfStrings();

MemoryStream ms = new MemoryStream();
TextWriter tw = new StreamWriter(ms);

foreach (string s in stringList) {
    tw.WriteLine(s);
}
tw.Flush();
byte[] bytes = ms.ToArray();
ms.Close();

Response.Clear();
Response.ContentType = "application/force-download";
Response.AddHeader("content-disposition", "attachment; filename=myFile.txt");
Response.BinaryWrite(bytes);
Response.End();

How can that be achieved in ASP.NET Core 1.1? Tried to follow File Providers in ASP.NET Core to no avail.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
nam
  • 21,967
  • 37
  • 158
  • 332

1 Answers1

1
MemoryReader mr = new MemoryStream();
TextWriter tw = new StreamWriter(mr);

foreach (string s in stringList) {
    tw.WriteLine(s);
}
tw.Flush();

return File(mr, "application/force-download", "myFile.txt")

Or directly write to the response:

HttpContext.Response.Body.WriteAsync(...);

Also viable alternative

TextWriter tw = new StreamWriter(HttpContext.Response.Body);

so you can directly write to the output string, without any additional memory usage. But you shouldn't close it, since it's the connection to the browser and may be needed further up in the pipeline.

Tseng
  • 61,549
  • 15
  • 193
  • 205
  • on `return File(tw,...)` line it's giving an error: `Cannot convert from TextWriter to byte[]` – nam May 31 '17 at 21:46
  • Then just pass the memory stream to it. File has an overload which accepts `Stream` – Tseng May 31 '17 at 21:46
  • Of course you can also do `new StreamWriter(HttpContext.Response.Body)` to write to the stream, instead of using `WriteAsync` which only accepts bytes – Tseng May 31 '17 at 21:56
  • 1
    I first declared `MemoryStream ms = new MemoryStream();`, then passed `ms` to `using(TextWriter tw = new StreamWriter(ms)){...}`. But then `return File(ms, ....)` returns blank page. However, in addition, if I also add `byte[] bytes = ms.ToArray(); and ms.Flush(); and then `return File(bytes, ....)` your code works. – nam May 31 '17 at 22:22
  • Calling ToArray should be unnecessary, as you would double your ram usage and the whole thing would be quite inefficient (write to memory, then copy it and only then pass it). Dunno about flush. Can't test right now, but flushing should be unnecessary unless it's a buffered stream – Tseng May 31 '17 at 22:26
  • Removing `Flush()` was fine but w/o `byte[] bytes = ms.ToArray();` and then calling `return File(ms, ....)` gives the error: `Cannot access a closed Stream` – nam May 31 '17 at 22:46
  • You shouldn't close it before you have read it. Also now thinking about it, you need to rewind the memory stream if you pass the stream to the File method, since after you written to it it'S at the end. Setting `.Position = 0` of the memory stream should do it. But liek I said, using the Responsestream for StreamWrite may be most effective as no memory allocation is necessary – Tseng May 31 '17 at 22:49
  • Following did the trick: `MemoryStream ms = new MemoryStream(); TextWriter tw = new StreamWriter(ms); await tw.WriteAsync(csv); tw.Flush(); ms.Position = 0; return File(ms, "application/force-download", "myFile.csv");` The `Using(...){...}` closes `ms` that was giving the error `Cannot access a closed Stream`. You may want to re-format your response for the benefit of other readers - although I did understand your thoughts due to our back and forth discussion. And I'll mark it as an `Answer`. Also, `new StreamWriter(HttpContext.Response.Body)` works but I needed to have user download file – nam May 31 '17 at 23:34