views:

332

answers:

2

First of all, I love ASP.NET MVC. This question is not a criticism of it. Rather, I want to confirm what I think I see and make sure I haven't missed something. Bear with me ... I can't get to the question without providing a little bit of context.

The question has to do with returning data in a Stream in response to an HTTP Post. In the olden days before ASP.NET MVC, you could do this by piping your data directly into the Response stream. For example, you might do something like this:

someObjectThatDumpsOutputToWhateverStreamYouHandIt.WriteTo(Response.OutputStream); 

Notice a key aspect of this code: I have not implemented ANY backing store. I did not have to stream the info to a byte array, or dump it into a temporary file. Rather, ASP.NET had already set up a Stream for the purpose of communicating the response back to the browser, and I have dumped the output I want directly into that Stream. This is more efficient in terms of CPU, memory, and execution time than copying all the data to some temporary location and then moving it into the Response stream.

However, it is not very friendly to unit tests. In fact, you can still do that kind of code in ASP.NET MVC, but the custom would be to avoid that. Rather, ASP.NET MVC would encourage you to return an ActionResult. The ActionResult is an ASP.NET MVC concept that exists mostly to be unit test friendly. It allows you to "declare" what you want done. A unit test can exercise a Controller action and confirm that it gets the expected ActionResult. That unit test can run outside a browser.

ASP.NET MVC offers a kind of ActionResult for returning Streams of data. It is called FileStreamResult. Don't let the word "File" in the name fool you. It is about returning a Stream of data, exactly as we are talking about above.

However, here's the problem, and the basis of my question: If you make your Controller method return a FileStreamResult, and you hand it a Stream that you want to return, then it appears there is no longer any way for you to dump the data directly to the Response stream. Now, it appears you are forced to make your own Stream with a backing store (such as memory or a file), dump your data into it, and then hand that Stream to the FileStreamResult that you return.

So it appears I have to do something like this (intentionally omitting dispose / using / etc.):

MemoryStream myIntermediateStream = new MemoryStream();
someObjectThatDumpsOutputToWhateverStreamYouHandIt.WriteTo(myIntermediateStream ); 

return new FileStreamResult(myIntermediateStream, "application/pdf");

Notice that myIntermediateStream causes the contents of the large stream of data to be stored in memory temporarily, so that the FileStreamResult can later on copy it again into the Response output stream.

So here's my question: Have I overlooked something, or is it accurate to say that using FileStreamResult forces you to have an intermediate storage location that you would not be forced to have if you were to write directly to the Response's output stream?

Thanks.

+2  A: 

I believe it would be more accurate to say that, assuming someObjectThatDumpsOutputToWhateverStreamYouHandIt does not inherit from System.IO.Stream, that using FileStreamResult forces you to either

a) implement a wrapper for someObjectThatDumpsOutputToWhateverStreamYouHandIt that does inherit from System.IO.Stream, or

b) use a temporary MemoryStream instance.

So, it is not necessarily less efficient, but does seem to require a bit more work for the purpose of returning a value.

Edit by Question Asker: I am choosing this question as the Accepted answer. Both responses were very helpful. I have to choose one, and this one pointed me to Phil Haack's DelegatingActionResult, which is exactly what I needed.

Joseph
Well, in this case, I'm calling a method on a 3rd-party library class that accepts a stream as a method parameter and writes to that. So it wouldn't make sense for it to inherit from Stream (unless I'm misunderstanding you). It doesn't matter which 3rd party library I'm using ... it's a fairly common pattern for libraries that produce big stream-like results to take a stream as a parameter and write directly to it. But is there some way inheriting from Stream will help solve the problem?
Charlie Flowers
The point to inheriting from Stream would be that you could then just hand your stream to the FileStreamResult. If the 3rd party library supports writing in chunks, you can implement a stream wrapper around the object that you could then return wrapped in a FileStreamResult.If the library doesn't support writing in chunks, then you might try creating your own subclass of ActionResult.
Joseph
Yes, making my own subclass of ActionResult might make sense. This particular library closes the stream before returning. I'd just have to make my ActionResult do it, and I could make it declarative and thus unit test friendly, I guess. Really, we need a "pull" interface, or lazy evaluation like Haskell or something. Hmmm. Plus 1, thanks.
Charlie Flowers
You're welcome. I was just looking around and found this link: http://haacked.com/archive/2008/05/10/writing-a-custom-file-download-action-result-for-asp.net-mvc.aspx . Just in case you hadn't seen it already. Also, it looks like @Aaronaught has another solution that might be worth looking at. I hadn't heard about PipeStream before.
Joseph
Yes, that's very helpful. I had started to toy with the idea of a lambda-based ActionResult, in order to defer the whole action till later. Not surprising that Phil is on top of it :)
Charlie Flowers
+1  A: 

I think the main idea behind FileStreamResult is that you use it when you already have a stream. If you have to create a temporary stream in order to use it, then there is no point; that's why there is also a FilePathResult and FileContentResult.

There are several instances when you might already have a stream:

  • Data stored in a SQL 2008 FILESTREAM column;
  • Data forwarded directly from another endpoint (NetworkStream);
  • A GzipStream or DeflateStream for sending something compressed;
  • Sending from a named pipe (PipeStream);
  • ...and so on.

The main use case for FileStreamResult (as I understand it) is when you have a pre-existing stream that you would need to read from and then write back to the response; instead of having to do the reading and writing and figuring out the buffering yourself, the FileStreamResult handles it for you.

So the short answer is no, FileStreamResult doesn't necessarily force you to have an "intermediate" storage location, if the stream is your data source. I wouldn't bother with FileStreamResult if the data is already in memory or on disk somewhere, ready and waiting to be written directly to the response stream.


I'd just like to add another clarification: The issue with this library is that it inverts the functionality you really need for FileStreamResult. Instead of handing you a stream that you can read from, it expects you to provide the stream so it can write to it. This is a common pattern, mind you, but it is not very well suited to the task.

If the stream contains a lot of data and you don't want to chew up memory with it, you should be able to invert the stream yourself using the PipeStream derivatives. Create either a named or anonymous pipe, create a server stream (which you can initialize with as small a buffer size as you want) and feed it to the library, then create a client stream on the same pipe and feed it to the FileStreamResult.

This gives you the unit-testability you want and lets you control exactly how much memory the process is allowed to use. It also has the added advantage of being able to produce the data asynchronously, which you might want if you're trying to chuck out gigabytes of data.

Aaronaught
You're giving me hope. I have a library with a method that wants me to hand it an empty stream. It will then fill that stream. I *definitely don't want* an intermediate storage location. But if I use FileStreamResult, it appears I *am* forced to have an intermediate storage. Are you saying that you see some way around that?
Charlie Flowers
@Charlie: If you trust the library to write directly to the response stream then I would simply give it that. This is *not* the primary use case for `FileStreamResult`; that result type is better for when you are simply *reading* from a stream that has been provided to you, not *writing* to an empty stream that you created. It's all about context; `FileStreamResult` has its uses but it doesn't sound like this is one of them.
Aaronaught
Cool, that makes sense. I agree that I can see good uses for it. It's just very helpful to get some confirmation that I'm not missing something, so thanks.
Charlie Flowers
@Charlie: I added a few more paragraphs that might help you. You can use pipes to create an "intermediate" storage location that doesn't need to store the entire blob in memory, it will buffer at whatever size you choose. Or you could create your own pseudo-pipe (stream buffer), since you're not doing IPC, but I always prefer to use the built-in Framework classes if it makes sense to do so.
Aaronaught