72

I have the following method:

public byte[] WriteCsvWithHeaderToMemory<T>(IEnumerable<T> records) where T : class
{
    using (var memoryStream = new MemoryStream())
    using (var streamWriter = new StreamWriter(memoryStream))
    using (var csvWriter = new CsvWriter(streamWriter))
    {
        csvWriter.WriteRecords<T>(records);

        return memoryStream.ToArray();
    }
}

Which is being called with a list of objects - eventually from a database, but since something is not working I'm just populating a static collection. The objects being passed are as follows:

using CsvHelper.Configuration;

namespace Application.Models.ViewModels
{
    public class Model
    {
        [CsvField(Name = "Field 1", Ignore = false)]
        public string Field1 { get; set; }

        [CsvField(Name = "Statistic 1", Ignore = false)]
        public int Stat1{ get; set; }

        [CsvField(Name = "Statistic 2", Ignore = false)]
        public int Stat2{ get; set; }

        [CsvField(Name = "Statistic 3", Ignore = false)]
        public int Stat3{ get; set; }

        [CsvField(Name = "Statistic 4", Ignore = false)]
        public int Stat4{ get; set; }
    }
}

What I'm trying to do is write a collection to a csv for download in an MVC application. Every time I try to write to the method though, the MemoryStream is coming back with zero length and nothing being passed to it. I've used this before, but for some reason it's just not working - I'm somewhat confused. Can anyone point out to me what I've done wrong here?

Cheers

Erik Schierboom
  • 16,301
  • 10
  • 64
  • 81
Ian Cotterill
  • 1,667
  • 4
  • 18
  • 28
  • 2
    Have you tried flushing your stream? put `csvWriter.Flush();` before you return. – Eli Gassert Nov 30 '12 at 13:24
  • Thank you. I knew it would be something daft, but I've lost half my morning staring at it unable to think of what was wrong... – Ian Cotterill Nov 30 '12 at 13:28
  • That worked? great. Would you like me to submit it as an answer for future searchers or will you submit the solution yourself? – Eli Gassert Nov 30 '12 at 13:30
  • If you could submit one and I'll check it answer so you get the credit. – Ian Cotterill Nov 30 '12 at 13:59
  • 1
    CsvField Attribute is not supported now, instead you can use [CsvClassMap](http://joshclose.github.io/CsvHelper/) and then register them as: csv.Configuration.RegisterClassMap(new MyClassMap); – Devesh Jul 22 '15 at 09:21

6 Answers6

83

You already have a using block which is great. That will flush your writer for you. You can just change your code slightly for it to work.

using (var memoryStream = new MemoryStream())
{
    using (var streamWriter = new StreamWriter(memoryStream))
    using (var csvWriter = new CsvWriter(streamWriter))
    {
        csvWriter.WriteRecords<T>(records);
    } // StreamWriter gets flushed here.

    return memoryStream.ToArray();
}

If you turn AutoFlush on, you need to be careful. This will flush after every write. If your stream is a network stream and over the wire, it will be very slow.

Josh Close
  • 22,935
  • 13
  • 92
  • 140
  • 2
    I think this is incorrect. If you do this, the StreamWriter will get flushed but it will also dispose of the underlying stream. – 2-bits Apr 29 '16 at 17:16
  • 1
    Yes, the return should be inside the using block. – Josh Close Apr 29 '16 at 17:20
  • Seems like CsvWriter has two bugs in version 3.0.0.0. It does not flush on Dispose. This is one thing and the second is that first record is not separated by new line from the header. So flushing csvWriter is a necessary workaround. – Radek Strugalski Oct 09 '17 at 17:23
  • 1
    Please submit a bug on GitHub and I'll fix ASAP – Josh Close Oct 09 '17 at 17:25
  • 1
    @RadekStrugalski Fixed the first issue. The second you need to call `NextRecord()` manually now. This is to give more control to the user. You can write a header and do some more manual writing this way. – Josh Close Oct 09 '17 at 20:51
  • Thank you it is not time saving but it is awesome than anything else – Hardik Masalawala Feb 24 '22 at 15:43
45

Put csvWriter.Flush(); before you return to flush the writer/stream.

EDIT: Per Jack's response. It should be the stream that gets flushed, not the csvWriter. streamWriter.Flush();. Leaving original solution, but adding this correction.

EDIT 2: My preferred answer is: https://stackoverflow.com/a/22997765/1795053 Let the using statements do the heavy lifting for you

Eli Gassert
  • 9,745
  • 3
  • 30
  • 39
31

Putting all these together (and the comments for corrections), including resetting the memory stream position, the final solution for me was;

        using (MemoryStream ms = new MemoryStream())
        {
            using (TextWriter tw = new StreamWriter(ms))
            using (CsvWriter csv = new CsvWriter(tw, CultureInfo.InvariantCulture))
            {
                csv.WriteRecords(errors); // Converts error records to CSV

                tw.Flush(); // flush the buffered text to stream
                ms.Seek(0, SeekOrigin.Begin); // reset stream position

                Attachment a = new Attachment(ms, "errors.csv"); // Create attachment from the stream
                // I sent an email here with the csv attached.
            }
        }

In case the helps someone else!

StudioLE
  • 656
  • 8
  • 13
Josh
  • 3,442
  • 2
  • 23
  • 24
  • 2
    `ms.Seek(0, SeekOrigin.Begin);` was the missing piece for me. Flushes, using statements, none of it made a difference until I reset the stream. – Brick Feb 12 '20 at 19:10
  • Answer not working and is possible outdated. CsvWriter(tw) no longer accepts only one argument, even when added it still throws an error complaining about closed memory stream. – brianc Oct 18 '21 at 01:01
9

There is no flush in csvWriter, the flush is in the streamWriter. When called

csvWriter.Dispose();

it will flush the stream. Another approach is to set

streamWriter.AutoFlush = true;

which will automatically flush the stream every time.

TuomasK
  • 1,849
  • 3
  • 13
  • 19
8

Here is working example:

void Main()
{
    var records = new List<dynamic>{
       new { Id = 1, Name = "one" },
       new { Id = 2, Name = "two" },
    };

    Console.WriteLine(records.ToCsv());
}

public static class Extensions {
    public static string ToCsv<T>(this IEnumerable<T> collection)
    {
        using (var memoryStream = new MemoryStream())
        {
            using (var streamWriter = new StreamWriter(memoryStream))
            using (var csvWriter = new CsvWriter(streamWriter))
            {
                csvWriter.WriteRecords(collection);
            } // StreamWriter gets flushed here.

            return Encoding.ASCII.GetString(memoryStream.ToArray());
        }
    }
}

Based on this answer.

Amit
  • 25,106
  • 25
  • 75
  • 116
0

using CsvHelper;
public class TwentyFoursStock
{
    [Name("sellerSku")]
    public string ProductSellerSku { get; set; }

    [Name("shippingPoint")]
    public string ProductShippingPoint { get; set; }
}

using (var writer = new StreamWriter("file.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
    csv.WriteRecords(TwentyFoursStock);
}