42

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.

Bob Tway
  • 9,301
  • 17
  • 80
  • 162
  • Try installing fiddler http://fiddler2.com/ . You will be able to see exactly what is being returned to the browser when this method executes. – mituw16 Jan 13 '14 at 14:09
  • Nothing comes back at all. However, I have discovered that it's connected to the action type - the method is currently set to POST because I wanted to send it some complex data. If I change it to GET, it works as expected. I can't see why POST is behaving differently though - it must still send a response? – Bob Tway Jan 13 '14 at 14:29

4 Answers4

56

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) })
Lin
  • 15,078
  • 4
  • 47
  • 49
  • This gives me the desired data in the response, and causes the browser to try and download the file (as desired) if it's made under a GET action. But if it's a POST action the browser does nothing with the response. – Bob Tway Jan 13 '14 at 16:00
  • 1
    hi @MattThrower, I'm not sure why you want to use POST method. Use GET you stil can pass a complex type parameter. – Lin Jan 13 '14 at 16:36
  • OK, well I got your code working by passing a ton of simple type parameters and querying the database. So thanks for that - but I'm curious to know how you'd pass a complex type reliant on a querystring - as JSON? – Bob Tway Jan 13 '14 at 16:46
  • when do you dispose the memorystream? – Lee Gary Sep 03 '14 at 06:38
  • @LeeGary the second memory stream instance is disposed by the `FileStreamResult` class – Filip Oct 11 '15 at 13:40
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");
HelloWorld
  • 3,381
  • 5
  • 32
  • 58
0

Try in the controller:

HttpContext.Response.AddHeader("content-disposition", "attachment; filename=payments.csv");
Marcin Wachulski
  • 567
  • 4
  • 14
0

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();
    }
}
Arun Prasad E S
  • 9,489
  • 8
  • 74
  • 87