skip to Main Content

I’ve been reading through:

https://www.aspsnippets.com/Articles/Export-data-from-SQL-Server-to-CSV-file-in-ASPNet-using-C-and-VBNet.aspx

Rather than only have the option to download as csv as described there in:

                //Download the CSV file.
                Response.Clear();
                Response.Buffer = true;
                Response.AddHeader("content-disposition", "attachment;filename=SqlExport.csv");
                Response.Charset = "";
                Response.ContentType = "application/text";
                Response.Output.Write(csv);
                Response.Flush();
                Response.End();

is there a way using native asp.net to first zip the csv output from the csv variable in Response.Output.Write(csv); so that the user downloads SqlExport.zip rather than SqlExport.csv?

2

Answers


  1. Have a look at the ZipArchive Class

    you can use public System.IO.Compression.ZipArchiveEntry CreateEntry (string entryName); to create an ZipEntry nd add it to an archive

    Login or Signup to reply.
  2. Roughly based on this, you can create a zip file while streaming it to the client;

    Response.ContentType = "application/octet-stream";
    Response.Headers.Add("Content-Disposition", "attachment; filename="SqlExport.zip"");
    
    using var archive = new ZipArchive(Response.Body, ZipArchiveMode.Create);
    
    var entry = archive.CreateEntry("SqlExport.csv");
    using var entryStream = entry.Open();
    
    entryStream.Write(csv); // write the actual content here
    
    entryStream.Flush();
    

    Though rather than appending to a single csv string, you should probably consider using a StreamWriter to write each snippet of text directly into the response stream. Substituting from your linked csv example;

    using var sw = new StreamWriter(entryStream);
    
    // TODO write header
    
    foreach (DataRow row in dt.Rows)
    {
        foreach (DataColumn column in dt.Columns)
        {
            //Add the Data rows.
            await sw.WriteAsync(row[column.ColumnName].ToString().Replace(",", ";") + ',');
        }
        //Add new line.
        await sw.WriteLineAsync();
    }
    

    Though that is a terrible example of a csv file. Rather than substituting ';' characters, the string should be quoted & all quotes escaped.

    However Response.Body is only available in .net 5 / core. To write directly to a http response in .net 4.8 or earlier, you’ll have to write your own HttpContent. Putting everything together, including a better csv formatter;

    public class ZipContent : HttpContent
    {
        private DataTable dt;
        private string name;
    
        public ZipContent(DataTable dt, string name = null)
        {
            this.dt = dt;
            this.name = name ?? dt.TableName;
            Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
            Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = $"{name}.zip"
            };
        }
    
        private string formatCsvValue(string value)
        {
            if (value == null)
                return "";
            if (value.Contains('"') || value.Contains(',') || value.Contains('r') || value.Contains('n'))
                return $""{value.Replace(""", """")}"";
            return value;
        }
    
        private IEnumerable<DataColumn> Columns()
        {
            // Why is this not already an IEnumerable<DataColumn>?
            foreach (DataColumn col in dt.Columns)
                yield return col;
        }
    
        protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            using var archive = new ZipArchive(stream, ZipArchiveMode.Create);
    
            var entry = archive.CreateEntry($"{name}.csv");
            using var entryStream = entry.Open();
            using var sw = new StreamWriter(entryStream);
    
            await sw.WriteLineAsync(
                string.Join(",",
                    Columns()
                    .Select(c => formatCsvValue(c.ColumnName))
                ));
    
            foreach (DataRow row in dt.Rows)
            {
                await sw.WriteLineAsync(
                    string.Join(",", 
                        row.ItemArray
                        .Select(o => formatCsvValue(o?.ToString()))
                    ));
            }
        }
    
        protected override bool TryComputeLength(out long length)
        {
            length = 0;
            return false;
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search