30

I'm trying to write to a text file in memory and then download that file without saving the file to the hard disk. I'm using the StringWriter to write the contents:

StringWriter oStringWriter = new StringWriter();
oStringWriter.Write("This is the content");

How do I then download this file?

EDIT: It was combination of answers which gave me my solution. Here it is:

StringWriter oStringWriter = new StringWriter();
oStringWriter.WriteLine("Line 1");
Response.ContentType = "text/plain";

Response.AddHeader("content-disposition", "attachment;filename=" + string.Format("members-{0}.csv",string.Format("{0:ddMMyyyy}",DateTime.Today)));
Response.Clear();

using (StreamWriter writer = new StreamWriter(Response.OutputStream, Encoding.UTF8))
{
    writer.Write(oStringWriter.ToString());
}
Response.End();
jwheron
  • 2,553
  • 2
  • 30
  • 40
higgsy
  • 1,991
  • 8
  • 30
  • 47

7 Answers7

20

This solved for me:

        MemoryStream ms = new MemoryStream();
        TextWriter tw = new StreamWriter(ms);
        tw.WriteLine("Line 1");
        tw.WriteLine("Line 2");
        tw.WriteLine("Line 3");
        tw.Flush();
        byte[] bytes = ms.ToArray();
        ms.Close();

        Response.Clear();
        Response.ContentType = "application/force-download";
        Response.AddHeader("content-disposition", "attachment;    filename=file.txt");
        Response.BinaryWrite(bytes);
        Response.End();     
Vinicius Sin
  • 1,571
  • 11
  • 8
17

Instead of storing the data in memory and then sending it to the response stream, you can write it directly to the response stream:

using (StreamWriter writer = new StreamWriter(Response.OutputStream, Encoding.UTF8)) {
  writer.Write("This is the content");
}

The example uses the UTF-8 encoding, you should change that if you are using some other encoding.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    Hi Guffa - how do I then get the response stream to download though? – higgsy Dec 31 '10 at 14:00
  • @higgsy: You add instructions for content disposition and content type in the header, so that the browser know what it is. Klaus Byskow Hoffman has some examples in his answer, but you don't have to use a HTTP handler to return the response, it works with a regular page also. You should specify the encoding in the content type also, for example `text/plain; charset=utf-8`. – Guffa Dec 31 '10 at 14:37
5

Basically you create an HttpHandler by implementing the IHttpHandler interface. In the ProcessRequest method you basically just write your text to context.Response. You also need to add a Content-Disposition http header:

context.Response.AddHeader("Content-Disposition", "attachment; filename=YourFileName.txt");

Also remember to set the ContentType:

context.Response.ContentType = "text/plain";
Klaus Byskov Pedersen
  • 117,245
  • 29
  • 183
  • 222
2

Just a small addition to the other answers. At the very end of a download I execute:

context.Response.Flush();
context.ApplicationInstance.CompleteRequest();

I learned that otherwise, the download sometimes does not complete successfully.

This Google Groups posting also notes that Response.End throws a ThreadAbortException which you could avoid by using the CompleteRequest method.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
  • 1
    actually when i used the lines you've added , the generated file contained the text i wrote in the stream plus the rendred html code so I had to get back in the code and just put the "Response.End " line and it worked just fine ! – Joy Jan 02 '13 at 09:22
  • 1
    Usually, I only do provide downloads through separate ASHX handlers, not in ASPX pages. – Uwe Keim Jan 02 '13 at 09:46
0

I had many issues with this. Finnaly found a solution that seems to work everytime.

In most cases the user is going to click a button for the download. At this point it is best to redirect the page back to the same spot. add a parameter in the url that you can grab and read.

example( www.somewhere.com/mypage.aspx?print=stuff)

    <asp:Button ID="btn" runat="server" Text="print something" OnClick="btn_Click" />


    protected void Page_Load(object sender, EventArgs e) {
        if (Request["print"] == "stuff") { Print("my test content"); }
    }

    /* or pass byte[] content*/
    private void Print(string content ){ 
        Response.ContentType = "text/plain";
        Response.AddHeader("content-disposition", "attachment;filename=myFile.txt");
        // Response.BinaryWrite(content);
        Response.Write(content);
        Response.Flush(); 
        Response.End();
    }

    protected void btn_Click(object sender, EventArgs e) {
        // postbacks give you troubles if using async.
        // Will give an error when Response.End() is called.
        Response.Redirect(Request.Url + "?print=queue");
    }
Mike
  • 623
  • 6
  • 26
0

Extension of @Vinicious answer.

I had data that could contain commas. The common solution is to escape that piece of data by enclosing it in quotes, while making sure to also escape quotes that could also be a part of the data.

One rub I came against and a warning when writing CSV, excel will not like you if you put spaces trailing your commas. discovered solution to my problem from superuser answer

protected void btnDownload_Click(object sender, EventArgs e)
{
    MemoryStream ms = new MemoryStream();
    TextWriter tw = new StreamWriter(ms, System.Text.Encoding.UTF8);
    var structures = KAWSLib.BusinessLayer.Structure.GetStructuresInService();
    // *** comma delimited
    tw.Write("Latitude, Longitude, CountySerial, StructureType, Orientation, District, RoutePre, RouteNo, LocationDesc");
    foreach (var s in structures)
    {
        tw.Write(Environment.NewLine + string.Format("{0:#.000000},{1:#.000000},{2},{3},{4},{5},{6},{7},{8}", s.LATITUDE, s.LONGITUDE, s.CO_SER, EscapeIfNeeded(s.SuperTypeLookup.SHORTDESC), EscapeIfNeeded(s.OrientationLookup.SHORTDESC), s.DISTRICT, s.ROUTE_PREFIX, s.RouteValue, EscapeIfNeeded(s.LOC_DESC)));
    }
    tw.Flush();
    byte[] bytes = ms.ToArray();
    ms.Close();

    Response.Clear();
    Response.ContentType = "application/force-download";
    Response.AddHeader("content-disposition", "attachment;    filename=" + string.Format("kaws-structures-{0:yyyy.MM.dd}.csv", DateTime.Today));
    Response.BinaryWrite(bytes);
    Response.End();
}

string EscapeIfNeeded(string s)
{
    if (s.Contains(","))
    {
        return "\"" + s.Replace("\"", "\"\"") + "\"";
    }
    else
    {
        return s;
    }
}

Below will cause a problem for excel. In excel the first quote will become part of the data and consequently than separate at the embedded comma. Spaces bad.

 tw.Write(Environment.NewLine + string.Format("{0:#.000000}, {1:#.000000}, {2}, {3}, {4}, {5}, {6}, {7}, {8}", s.LATITUDE, s.LONGITUDE, s.CO_SER, EscapeIfNeeded(s.SuperTypeLookup.SHORTDESC), EscapeIfNeeded(s.OrientationLookup.SHORTDESC), s.DISTRICT, s.ROUTE_PREFIX, s.RouteValue, EscapeIfNeeded(s.LOC_DESC)));
Community
  • 1
  • 1
eric1825
  • 405
  • 3
  • 13
-1

This is very simple, and the answer can be seen in this Microsoft KB Article: How to write binary files to the browser using ASP.NET and C#

Joel Martinez
  • 46,929
  • 26
  • 130
  • 185