'Using CSVHelper to output stream to browser

I'm trying to use CSVHelper to generate a CSV file and send it back to a browser, so the user can select a save location and filename and save the data.

The website is MVC based. Here' the jQuery button code I'm using to make the call (data is some serialised Json representation of a DTO list):

    $.ajax({
        type: "POST",
        url: unity.baseUrl + "common/ExportPayments",
        data: data
    });

Here's the controller code:

    [HttpPost]
    public FileStreamResult ExportPayments()
    {
        MemoryStream ms = new MemoryStream();
        StreamWriter sw = new StreamWriter(ms);
        CsvWriter writer = new CsvWriter(sw);

        List<Payment_dto> pd = _commonService.GetPayments();

        foreach (var record in pd)
        {
            writer.WriteRecord(record);
        }
        sw.Flush();

        return new FileStreamResult(ms, "text/csv");
    }

Which seems to achieve precisely nothing - invoking the method steps into the correct bit of code but the response is empty, let alone offering the user a file dialog to save the data. I've stepped through this code, and it brings back data from the service, writes it, and throws no errors. So what am I doing wrong?

EDIT: Returning this ...

return File(ms.GetBuffer(), "text/csv", "export.csv");

... gives me a response, consisting of the csv-formatted data that I'm expecting. But the browser still doesn't seem to know what to do with it - no download option is offered to the user.



Solution 1:[1]

Try below code:

    public FileStreamResult  ExportPayments()
    {
        var result = WriteCsvToMemory(_commonService.GetPayments()()); 
        var memoryStream = new MemoryStream(result);
        return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
    }


    public byte[] WriteCsvToMemory(IEnumerable<Payment_dto> records)
    {
        using (var memoryStream = new MemoryStream())
        using (var streamWriter = new StreamWriter(memoryStream))
        using (var csvWriter = new CsvWriter(streamWriter))
        {
            csvWriter.WriteRecords(records);
            streamWriter.Flush();
            return memoryStream.ToArray();
        }
    }

Update

Below is how to pass a complex type model to an action method which is using GET HTTP method. I don't prefer this approach, it just gives you an idea there is an approach to achieve this.

Model

    public class Data
    {
        public int Id { get; set; }
        public string Value { get; set; }

        public static string Serialize(Data data)
        {
            var serializer = new JavaScriptSerializer();
            return serializer.Serialize(data);
        }
        public static Data Deserialize(string data)
        {
            var serializer = new JavaScriptSerializer();
            return serializer.Deserialize<Data>(data);
        }
    }

Action:

    [HttpGet]
    public FileStreamResult ExportPayments(string model) 
    {
        //Deserialize model here 
        var result = WriteCsvToMemory(GetPayments()); 
        var memoryStream = new MemoryStream(result);
        return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
    }

View:

@{
    var data = new Data()
    {
        Id = 1,
        Value = "This is test"
    };
}
@Html.ActionLink("Export", "ExportPayments", new { model = Data.Serialize(data) })

Solution 2:[2]

ASP.NET Core solution:

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8); // No 'using' around this as it closes the underlying stream. StreamWriter.Dispose() is only really important when you're dealing with actual files anyhow.

using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture, true)) // Note the last argument being set to 'true'
    csvWriter.WriteRecords(...);

streamWriter.Flush(); // Perhaps not necessary, but CsvWriter's documentation does not mention whether the underlying stream gets flushed or not

memoryStream.Position = 0;

Response.Headers["Content-Disposition"] = "attachment; filename=somename.csv";

return File(memoryStream, "text/csv");

Solution 3:[3]

Try in the controller:

HttpContext.Response.AddHeader("content-disposition", "attachment; filename=payments.csv");

Solution 4:[4]

Could also user dynamic keyword for converting any data

Code from @Lin

public FileStreamResult  ExportPayments()
{
    var result = WriteCsvToMemory(_commonService.GetPayments()()); 
    var memoryStream = new MemoryStream(result);
    return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}


public byte[] WriteCsvToMemory(dynamic records)
{
    using (var memoryStream = new MemoryStream())
    using (var streamWriter = new StreamWriter(memoryStream))
    using (var csvWriter = new CsvWriter(streamWriter))
    {
        csvWriter.WriteRecords(records);
        streamWriter.Flush();
        return memoryStream.ToArray();
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 HelloWorld
Solution 3 Marcin Wachulski
Solution 4 Arun Prasad E S