ediblecode
ediblecode

Reputation: 11971

StreamWriter writing to MemoryStream

I was under the impression that when you called Flush() in a StreamWriter object it writes to the underlying stream, but apparently this isn't the case with my code.

Instead of writing to my file it will just write nothing. Any ideas where I'm going wrong?

    public FileResult DownloadEntries(int id)
    {
        Competition competition = dataService.GetCompetition(id);
        IQueryable<CompetitionEntry> entries = dataService.GetAllCompetitionEntries().Where(e => e.CompetitionId == competition.CompetitionId);

        MemoryStream stream = new MemoryStream();
        StreamWriter csvWriter = new StreamWriter(stream, Encoding.UTF8);

        csvWriter.WriteLine("First name,Second name,E-mail address,Preferred contact number,UserId\r\n");

        foreach (CompetitionEntry entry in entries)
        {
            csvWriter.WriteLine(String.Format("{0},{1},{2},{3},{4}",
                entry.User.FirstName,
                entry.User.LastName,
                entry.User.Email,
                entry.User.PreferredContactNumber,
                entry.User.Id));
        }

        csvWriter.Flush();

        return File(stream, "text/plain", "CompetitionEntries.csv");
    }

Upvotes: 27

Views: 70360

Answers (3)

Alexei Levenkov
Alexei Levenkov

Reputation: 100620

Your MemoryStream is positioned at the end. Better code would be to create new R/o memory stream on the same buffer using MemoryStream(Byte[], Int32, Int32, Boolean) constructor.

Simplest r/w on trimmed buffer:

 return File(new MemoryStream(stream.ToArray());

R/o without copying the internal buffer:

 return File(new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, false);

Note: be careful not to dispose the stream you are returning via File(Stream). Otherwise you'll get "ObjectDisposedException" of some sort. I.e. if you simply set position of the original stream to 0 and wrap StreamWriter into using you'll get into returning disposed stream.

Upvotes: 10

David Thielen
David Thielen

Reputation: 33006

I believe you need to set Stream.Position = 0. When you write, it advances the position to the end of the stream. When you pass it to File() it starts from the position it is at - the end.

I think the following will work (did not try to compile this):

stream.Position = 0;
return File(stream, "text/plain", "CompetitionEntries.csv");

And this way you are not creating any new objects or copying the underlying array.

Upvotes: 32

neontapir
neontapir

Reputation: 4736

In playing with this, I got the following prototype to work:

using System.Web.Mvc;
using NUnit.Framework;

namespace StackOverflowSandbox
{
[TestFixture]
public class FileStreamResultTest
{
    public FileStreamResult DownloadEntries(int id)
    {
        // fake data
        var entries = new[] {new CompetitionEntry { User = new Competitor { FirstName = "Joe", LastName = "Smith", Email = "[email protected]", Id=id.ToString(), PreferredContactNumber = "555-1212"}}};

        using (var stream = new MemoryStream())
        {
            using (var csvWriter = new StreamWriter(stream, Encoding.UTF8))
            {
                csvWriter.WriteLine("First name,Second name,E-mail address,Preferred contact number,UserId\r\n");

                foreach (CompetitionEntry entry in entries)
                {
                    csvWriter.WriteLine(String.Format("{0},{1},{2},{3},{4}",
                                                      entry.User.FirstName,
                                                      entry.User.LastName,
                                                      entry.User.Email,
                                                      entry.User.PreferredContactNumber,
                                                      entry.User.Id));
                }

                csvWriter.Flush();
            }

            return new FileStreamResult(new MemoryStream(stream.ToArray()), "text/plain");
        }
    }

    [Test]
    public void CanRenderTest()
    {
        var fileStreamResult = DownloadEntries(1);
        string results;
        using (var stream = new StreamReader(fileStreamResult.FileStream))
        {
            results = stream.ReadToEnd();
        }
        Assert.IsNotEmpty(results);
    }
}

public class CompetitionEntry
{
    public Competitor User { get; set; }
}

public class Competitor
{
    public string FirstName;
    public string LastName;
    public string Email;
    public string PreferredContactNumber;
    public string Id;
}
}

Upvotes: 3

Related Questions