Search code examples
asp.net-coresmtpclientcsvreader

ASP.NET Core: Sending a CSV email attachment


I'm trying to create a CSV file (using the popular CsvHelper library) in memory and attach it to an email. See the following code:

public async Task<IActionResult> Send() {
    using var memoryStream = new MemoryStream();

    await CreateAsync(memoryStream, new List<Foo>() {
        new Foo()
    });

    await SendAsync(message => {
        message.Attachments.Add(new Attachment(memoryStream), "file.csv", "text/csv");
    });

    return Content("Sent!");
}

private async Task CreateAsync<T>(Stream stream, IEnumerable<T> records) {
    using var writer = new StreamWriter(stream);
    using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
    await csv.WriteRecordsAsync(records);
}

private async Task SendAsync(Action<MailMessage>? options = null) {
    using var client = new SmtpClient("xxx", 25);
    using var mailMessage = new MailMessage("[email protected]", "[email protected]");
    options?.Invoke(mailMessage);
    await client.SendMailAsync(mailMessage);
}

This throws the error:

NotSupportedException: Stream does not support reading.

However if I try the following code to attach the file instead, then it works fine:

message.Attachments.Add(new Attachment(new MemoryStream(memoryStream.ToArray()), "file.csv", "text/csv"));

I'd appreciate if someone could help explain why the first example did not work, and how I can update it so that I don't have to create two MemoryStreams.


Solution

  • You are disposing the StreamWriter and this disposes the underlying stream, from the docs - The StreamWriter object calls Dispose() on the provided Stream object when StreamWriter.Dispose is called.

    You can stop this from happening by using the leaveOpen option.

    using var writer = new StreamWriter(stream, leaveOpen: true);