0

Who know how to do this? I have a loop and in that loop a List is build up. I want each List to be saved as a different file with a different filename (duh). I am using HttpContext.Current.Response for this. A single file works fine, however, I cannot loop it. The error I receive upon the second iteration is "Server cannot clear headers after HTTP headers have been sent." My code is somewhat like below. What matters of course is the part commented by // response to user. Any comments highly appreciated!

Cheerz, Ronald

        foreach (GridViewRow dr in GridView1.Rows)
        {
            // find the check box in the row
            System.Web.UI.WebControls.CheckBox cb = (System.Web.UI.WebControls.CheckBox)dr.FindControl("chkbx_selected");

            // see if it's checked
            if (cb != null && cb.Checked)
            {
                // get the cell
                DataControlFieldCell Cell = GetCellByName(dr, "GUID");
                string guid = new Guid(Cell.Text);

                // get the record filtered on the GUID
                var result_json = call_WEBAPI(guid);

                // result_json contains serialized List<string>
                List<string> result = JsonConvert.DeserializeObject<List<string>>(result_json);

                // response to user
                string filename = result[0] + ".txt";
                HttpContext.Current.Response.Clear();
                HttpContext.Current.Response.ClearHeaders();
                HttpContext.Current.Response.AppendHeader("content-disposition", string.Format("attachment; filename={0}", filename));
                HttpContext.Current.Response.ContentType = "application/octet-stream";
                foreach (string line in result)
                {
                    HttpContext.Current.Response.Write(line + Environment.NewLine);
                }

                HttpContext.Current.Response.Flush();

            }
        }

        HttpContext.Current.Response.End();

Update: I now create a zip archive in memory, create an entry and fill this with a writer. But how do I create the second entry in the .zip?? The code below does not work, error is "Entries in create mode may only be written to once, and only one entry may be held open at a time."

        using (MemoryStream zipToOpen = new MemoryStream())
        {
            using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Create, true))
            {
                ZipArchiveEntry readmeEntry = archive.CreateEntry("Readme.txt");
                using (StreamWriter writer = new StreamWriter(readmeEntry.Open()))
                {
                    writer.WriteLine("Information about this package.");
                    writer.WriteLine("========================");
                }

                List<string> result = new List<string>();

                result.Add("line1");
                result.Add("line2");

                ZipArchiveEntry readmeEntry2 = archive.CreateEntry("Readme2.txt");
                using (StreamWriter writer = new StreamWriter(readmeEntry.Open()))
                {
                    writer.Write(result);
                }

            }

            using (var fileStream = new FileStream(@"C:\test.zip", FileMode.Create))
            {
                zipToOpen.Seek(0, SeekOrigin.Begin);
                zipToOpen.CopyTo(fileStream);
            }
        }
Ronald
  • 1,083
  • 3
  • 13
  • 20
  • If you want, you may follow the walk-through with jquery http://stackoverflow.com/questions/9047645/download-multiple-files-without-using-zip-file – ken lacoste Jul 08 '15 at 12:01
  • I do not know anything about jquery. Normally I just work on the API back ends, but I needed a simple GUI. But I can try to use the post, thanks. – Ronald Jul 08 '15 at 13:16

3 Answers3

5

A single response can contain only a single "file". (Technically HTTP has no concept of "files", which is kind of why this is the case. HTTP has "headers" and "content". As the error tells you, once the headers are sent you can't change them. Because they've already been sent to the client.)

You're either going to have to return the files from multiple requests/responses or compress them into a single file and return that.

(Also, why use "application/octet-stream" for a text file? Use that for the zipped file perhaps, but a text file should probably be "text/plain" or something similar.)

Community
  • 1
  • 1
David
  • 208,112
  • 36
  • 198
  • 279
  • yeah, figured out that this somehow should be the root cause. But how do I generate multiple requests/responses from clicking one button on an asp page? Can I use Tasks orso here (each file = one thread)? And "applications/octet-stream" forces the browser to save the file instead of opening them. – Ronald Jul 08 '15 at 13:15
  • @Ronald: The client has to initiate subsequent requests, the server can't send multiple responses to a single request. So you'd need some client-side code to do that. The zip file is generally the simpler solution. And if you want to tell the client to save the file instead of display it, that's what the `Content-Disposition` header is for. Which you're already using. – David Jul 08 '15 at 13:20
  • ok ,thanks, guess it's going to be the zip variant then. Is the approach like I described? Temp folder with the files on the back end, zip, and then send the zip file? I can't somehow do the temp stuff in memory and zip that? – Ronald Jul 08 '15 at 14:41
0

You can zip all files on the server and then send one .zip file. As Google does :) It'll solve you problem

Lokki
  • 372
  • 1
  • 6
  • 20
  • So basically create a temp dir, dump the files there, zip the temp dir and send this in response.write to the end user? Ugly IMHO. – Ronald Jul 08 '15 at 13:21
  • You can just create empty zip in memory, put files in there and send it to user. It'll reduce the size and you can be sure that file will be downloadable for user, because browser will block more than 1 file and ask user whether he allows to download multiple files – Lokki Jul 08 '15 at 13:31
  • ok, do you have an example of doing this in memory? I will add a method on the backend that sends a file to the user to download then. – Ronald Jul 08 '15 at 14:47
  • in other words, on the backend, can I create a zip file in memory, create the other files in memory, and add those last files to the zip file in memory? And then send the zip to the calling end user? It will fit in memory, results are not that big. – Ronald Jul 08 '15 at 14:52
  • Allright, I got this working, but how do I add the second file to the archive? please see my original post and it's update. – Ronald Jul 09 '15 at 09:44
  • Nevermind, got it working, now I need the List properly written in the StreamWriter as individual lines. Guess a loop is not the best thing to do here performance wise, looking for an alternative. – Ronald Jul 09 '15 at 09:56
0

while zip file opening it is anoying which is always open.! But if you try to close there is just one manner dispose but this time it is anoying disposed object. That is the solution Dispose and reopen all files right below one opening example

       string MappingpathZip = Path.Combine(HttpRuntime.AppDomainAppPath, @"XmlDocs\Invs\" + firmName + fileName + ".zip");
        ZipArchive zip = ZipFile.Open(MappingpathZip, ZipArchiveMode.Create);
        ZipArchiveEntry zae = zip.CreateEntryFromFile(MappingpathSignedXML, Path.GetFileName(MappingpathZip), CompressionLevel.Optimal);

        var stream = zae.Open();
        byte[] bytes;
        using (var ms = new MemoryStream())
        {
            stream.CopyTo(ms);
            bytes = ms.ToArray();
        }
Hamit YILDIRIM
  • 4,224
  • 1
  • 32
  • 35