MVC Controller Using Response Stream
Asked Answered
L

4

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.

Luckey answered 23/8, 2011 at 15:21 Comment(2)
Create a custom ActionResult type as detailed in this post: [#990427 [1]: #990427Ito
Looks indeed as a duplicate of https://mcmap.net/q/190316/-writing-to-output-stream-from-action/1178314 which has a quite good answer imho (https://mcmap.net/q/190316/-writing-to-output-stream-from-action)Traver
W
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");
}
Wellheeled answered 5/6, 2015 at 15:54 Comment(3)
are you sure about using (serverPipe)? it will be used in FileStreamResult(clientPipe. What happens if the task runs quickly?Largely
@Largely 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.Wellheeled
"[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.Heptameter
L
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...

Luckey answered 2/9, 2011 at 17:38 Comment(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.Traver
W
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");
}
Wellheeled answered 5/6, 2015 at 15:54 Comment(3)
are you sure about using (serverPipe)? it will be used in FileStreamResult(clientPipe. What happens if the task runs quickly?Largely
@Largely 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.Wellheeled
"[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.Heptameter
P
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

Phenomena answered 23/8, 2011 at 18:46 Comment(6)
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.Luckey
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.Phenomena
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");Luckey
Didn't understand your question.Phenomena
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.Luckey
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.Phenomena
M
-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);
}
Motorcar answered 2/10, 2013 at 16:18 Comment(1)
This entirely defeats the purpose of Streams. Please don't do this in production code.Fugato

© 2022 - 2024 — McMap. All rights reserved.