5

For my mocking purposes I'd like to output a FileResult to an actual file on disk.

Is there a way to get the contents of a FileResult and write it to a file? The exposed properties on a FileResult are a bit on the slim side.

I'm looking for something like:

var file_result = controller.CsvExport();

file_result.ToFile(@"C:\temp.csv");

public static void WriteToFile(this FileResult fileResult, string fileName)
{
    // what to implement here?
    [...]
}
mason
  • 31,774
  • 10
  • 77
  • 121
Dirk Boer
  • 8,522
  • 13
  • 63
  • 111
  • shouldn't you even mock the file IO so it doesn't even go to disk? – Daniel A. White Mar 11 '15 at 13:47
  • you could decouple the generation of the content and test that. – Daniel A. White Mar 11 '15 at 13:47
  • Ok, situation is a bit more complicated than this, and it's not only for testing but also for development. If it's not possible, it's not possible, but for now I'd like to see if we are able to make it work this way before we look for alternatives. – Dirk Boer Mar 11 '15 at 13:51
  • Whatever happens inside `CsvExport()` should be decoupled from the controller, so that you can get its return value as an object that is not a `FileResult`. – Steven Liekens Mar 11 '15 at 14:08
  • In other words: `controller.CsvExport()` should be a thin wrapper that does nothing except convert existing files to `FileResult`. – Steven Liekens Mar 11 '15 at 14:09
  • @StevenLiekens I guess your comments are well meant, but I think it's up to the questioner (me in this case) to decide what should be in the controller, and what deserves it's own class or method. Anyway, apparently there was a proper answer, see below. Thanks! – Dirk Boer Mar 11 '15 at 14:21
  • You're trying to test business logic, but it's not exposed outside of the controller. That's a gross violation of the MVC pattern. – Steven Liekens Mar 11 '15 at 14:40
  • FYI: the only logic in this controller is preparing a view. In this case the view is a CSV file, instead of a HTML file. FYI2: talking down on others doesn't make you a better programmer. There is an answer to this question, I'm happy, mason is happy, and people with the same question in the future will be happy. Rainbows! – Dirk Boer Mar 11 '15 at 15:07
  • I'm not talking down on you. I'm providing constructive feedback. – Steven Liekens Mar 11 '15 at 15:17

1 Answers1

12

When you return the FileResult, use one of these subclasses...

Then you can access the file via...

  • FileContentResult.FileContents byte[]
  • FilePathResult.FileName string/path
  • FileStreamResult.FileStream stream

Alternatively in your extension method, check to see which subtype it is, cast it to the subtype, grab the file path/contents/stream and write it to your output file.

public static void ToFile(this FileResult fileResult, string fileName)
{
    if (fileResult is FileContentResult)
    {
        File.WriteAllBytes(fileName, ((FileContentResult)fileResult).FileContents);
    }
    else if (fileResult is FilePathResult)
    {
        File.Copy(((FilePathResult)fileResult).FileName, fileName, true); //overwrite file if it already exists
    }
    else if (fileResult is FileStreamResult)
    {
        //from http://stackoverflow.com/questions/411592/how-do-i-save-a-stream-to-a-file-in-c
        using (var fileStream = File.Create(filename))
        {
            var fileStreamResult = (FileStreamResult)fileResult;
            fileStreamResult.FileStream.Seek(0, SeekOrigin.Begin);
            fileStreamResult.FileStream.CopyTo(fileStream);
            fileStreamResult.FileStream.Seek(0, SeekOrigin.Begin); //reset position to beginning. If there's any chance the FileResult will be used by a future method, this will ensure it gets left in a usable state - Suggestion by Steven Liekens
        }
    }
    else
    {
        throw new ArgumentException("Unsupported FileResult type");
    }
}

By the way, if I were you, I'd call it ToFileOnDisk instead of ToFile so that it's clearer where the output will go.

mason
  • 31,774
  • 10
  • 77
  • 121
  • Ah, just added the extension method I wrote after your answer :) Yours looks a bit clearer though! Thanks a lot. – Dirk Boer Mar 11 '15 at 14:27
  • The third if-branch should probably reset the position of the stream _after_ instead of before reading from it. – Steven Liekens Mar 11 '15 at 14:44
  • @StevenLiekens It should definitely be done before, because the [MSDN documentation for CopyTo()](https://msdn.microsoft.com/en-us/library/dd782932(v=vs.110).aspx) says "Copying begins at the current position in the current stream, and does not reset the position of the destination stream after the copy operation is complete.". You might want to also reset the stream afterwards, but it does definitely need to be done before. – mason Mar 11 '15 at 14:46
  • Not resetting the stream would leave the `FileResult` in an unusable state. That is: the response would be empty, because the stream reached the end of the file. Also, before reading for the first time, the current position is probably 0 anyway. – Steven Liekens Mar 11 '15 at 14:48
  • @StevenLiekens I understand that, which is why I also said you might want to reset the stream afterwards. You've made your point, no need to repeat it! – mason Mar 11 '15 at 14:50
  • Thanks @mason, goes into my extension method library. I renamed the method because `ToFile` was indeed a bit unclear. – Dirk Boer Mar 11 '15 at 15:12