attaching a collection of strings to an email as a text file

Remember that previous post about List<string>.Add?  Well, one of the uses of the messages was to get into an email.

Unfortunately, there’s not an Attachment ctor that takes the contents of a file – it’s all around passing in a filename or a stream.  Since I don’t want to have to write this out to disk, stream’s the way to go.

NOTE: you would be forgiven if you read the example code in the string-param ctor and thought it actually *did* take content as the param.  I’m not sure the author of the example knew that the param was a filename, to be honest (although it’s possible the example was written during the 2.0 cycle when that ctor really did take content).  The example’s been busted for awhile. :)  Even the second comment here is wrong – it’s not in the body, it’s an attachment! (API perspective, ignore mime encoding behavior 🙂

// Attach the message string to this e-mail message.
Attachment data = new Attachment(textMessage);
// Send textMessage as part of the e-mail body.
message.Attachments.Add(data);

 

Creating the Content

Now, on to getting the messages into file content.  Sure you could foreach and WriteLine something (for instance, new StreamWriter(new MemoryStream) then Position = 0 the stream afterwards for the ctor or whatever), but I like more declarative-ish constructs and like to actually be able to easily see (my infinitives are very flexible – they can do a split!) the file content in the debugger, so:

var testingContent = String.Concat(testingMessages
    .Select(s => s + Environment.NewLine) // append a newline to each
    .ToArray()); // make array so Concat can take it

Admittedly that last line is annoying, but it’s only there because this is currently built against 3.5 – in 4.0 String.Concat (and String.Join) thankfully added IEnumerable overloads

Then it’s a matter of constructing a stream (I’d use UTF8 or Unicode normally, but wanted ASCII for this)

new MemoryStream(Encoding.ASCII.GetBytes(testingContent))

then passing it to the ctor (and then adding that to the message)

message.Attachments.Add(new Attachment(testingMemoryStream, "text/plain")
    {
        Name = "testingMessages.txt",
    });

Attachments and Disposed Streams

One minor gotcha you may run across – the Attachment (smartly) doesn’t pull anything out of the stream at ctor time – the data will be fetched when you actually send the message (since at that point it needs the data to actually write the outgoing email), so while you should do a using() or similar to dispose the MemoryStream you create, you need to make sure that doesn’t happen until after the email is sent – otherwise you’ll get an exception when it tries to read the disposed stream :) 

You may be tempted to using (new MemoryStream) { message.Attachments.Add(…); } smtpClient.Send(message); but don’t do it! 🙂

Memory Usage?

Since someone will likely point it out – I know the memory behavior of this isn’t great – we end up with the same content in 3 places: 1) the List<string> 2) the file content 3) the memory stream’s byte buffer.  You could certainly have created the MemoryStream, wrapped it in a StreamWriter, then written the messages to it instead of the List<string>, reset the position before passing to the ctor, and been down to 1 copy of the contents in memory instead of 3.

Mitigating factors are: 1) This code path isn’t used in production and 2) we’re not keeping the copies around very long – the first 2 copies are free to be GC’d after the Add of the attachment since they’re local vars that go out of scope.

Advertisements