11

I'm using MVC 3 I would like to dynamically create a CSV file for download, but I am unsure as to the correct MVC orientated approach.

In conventional ASP.net, I would have written something like:

Response.ClearHeaders();
Response.ContentType = "text/csv";
Response.AddHeader("content-disposition", attachment;filename='Test.csv'");
Response.Write("1,2,3");
Response.End();

I have looked at the ContentResult action but it appears that I would need to create the result as a string, i.e.

return Content(myData, "text/csv");

I could, I suppose, build a string, but since these files could be several thousand lines long, this seems inefficient to me.

Could someone point me in the right direction? Thanks.

Michael
  • 8,362
  • 6
  • 61
  • 88
Neilski
  • 4,385
  • 5
  • 41
  • 74
  • Create a custom ActionResult type as detailed in this post: [http://stackoverflow.com/questions/989927/recommended-way-to-create-an-actionresult-with-a-file-extension?rq=1][1] [1]: http://stackoverflow.com/questions/989927/recommended-way-to-create-an-actionresult-with-a-file-extension?rq=1 – Bron Davies May 28 '14 at 18:34
  • Looks indeed as a duplicate of http://stackoverflow.com/q/943122/1178314 which has a quite good answer imho (http://stackoverflow.com/a/13456219/1178314) – Frédéric May 12 '15 at 16:07

4 Answers4

15

I have found one possible solution to this problem. You can simply define the action method to return an EmptyResult() and write directly to the response stream. For example:

public ActionResult RobotsText() {
    Response.ContentType = "text/plain";
    Response.Write("User-agent: *\r\nAllow: /");
    return new EmptyResult();
}

This seems to work without any problems. Not sure how 'MVC' it is...

Andrew Garrison
  • 6,977
  • 11
  • 50
  • 77
Neilski
  • 4,385
  • 5
  • 41
  • 74
  • 1
    It is a hack, a MVC controller is not supposed to write directly to response stream. This is supposed to be the job of ExecuteResult method of an action result object. – Frédéric May 12 '15 at 15:46
8

I spent some time on the similar problem yesterday, and here's how to do it right way:

public ActionResult CreateReport()
{
    var reportData = MyGetDataFunction();
    var serverPipe = new AnonymousPipeServerStream(PipeDirection.Out);
    Task.Run(() => 
    {
        using (serverPipe)
        {
             MyWriteDataToFile(reportData, serverPipe)
        }
    });

    var clientPipe = new AnonymousPipeClientStream(PipeDirection.In,
             serverPipe.ClientSafePipeHandle);
    return new FileStreamResult(clientPipe, "text/csv");
}
galets
  • 17,802
  • 19
  • 72
  • 101
  • are you sure about using (serverPipe)? it will be used in FileStreamResult(clientPipe. What happens if the task runs quickly? – Slava Sep 18 '15 at 09:34
  • @Slava system will manage data flow between two ends of pipe, so that should not be a concern. It won't let task close server pipe until it's capable of consuming all data. In other words, task will not exit abruptly, it will be held at either write, or at close stream. Try it, it works. – galets Sep 26 '15 at 21:16
  • 3
    "[AnonymousPipeClientStream] pipes help provide safe and secure *interprocess* communication between child and parent processes." - seems a bit beyond what is required for same-process execution. – user2864740 Mar 28 '19 at 18:35
3

Try returning one the FileResults: http://msdn.microsoft.com/en-us/library/system.web.mvc.fileresult.aspx

Also see this example: http://forums.asp.net/t/1491579.aspx/1

Ofer Zelig
  • 17,068
  • 9
  • 59
  • 93
  • I'm currently using something similar with File(Encoding.UTF8.GetBytes(sb.ToString()), "text/csv", fileName) but it still seems like a potential problem to build the whole output in memory before committing to the view/response. – Neilski Aug 23 '11 at 19:58
  • Investigate the other overloads of File(). Please have a look at the links I posted. Some of the overloads don't require you to build the file in-memory. – Ofer Zelig Aug 23 '11 at 20:06
  • Isn't building a byte array (byte[]) still finalising the output data in memory before committing it to the File ActionResult? Example: return File(imageData,"image/jpeg","fileName.jpg"); – Neilski Aug 24 '11 at 04:48
  • Didn't understand your question. – Ofer Zelig Aug 24 '11 at 09:52
  • Sorry, Ofer - thanks for your persistence... In the examples you referenced, the data store creates a byte[] which is then passed to the File() method, so, I think, the object is still created/loaded into memory before it is written to the view. I (think) I was looking for something more like being able to write raw data to the Response Stream. – Neilski Aug 25 '11 at 15:38
  • 3
    No - pay attention to 2 overloads of File() which accept a path for a file stored on your disk. You don't make a byte[] yourself in these overloads. – Ofer Zelig Aug 25 '11 at 15:51
-2

Try something like this:

public ActionResult CreateReport(string report, string writer)
{
    var stream = new MemoryStream();
    var streamWriter = new StreamWriter(stream);

    _generateReport.GenerateReport(report, writer);

    streamWriter.Flush();
    stream.Seek(0, SeekOrigin.Begin);

    return new FileStreamResult(stream, writer.MimeType);
}
cedd
  • 1,741
  • 1
  • 21
  • 34