DISCLAIMER:
Although the technology discussed in this article is interesting—that is, the ability to convert a string into a stream—the usage isn’t correct. Rather than going through all this effort to create an attachment based on a string, you can simply call the Attachment.CreateAttachmentFromString method. The author discovered this after the article went to print, apologizes for his error, and hopes that you find the information in this article interesting, if not completely useful.
You know what drives me nuts (among so many other things)? What makes me crazy, this week, is the requirement, in so many areas of programming, that you must serialize data into a file on disk before you can programmatically work with that data. Don’t believe that this is an issue?
Here’s a challenge for you: Store an image in a database somewhere. Now, using the language of your choice, write code that retrieves that image and creates a new picture box on a slide, in Microsoft PowerPoint, that contains that image. I tried. Perhaps I missed the point, but the only way I could accomplish this task was to store the image into a disk file, and then retrieve the image from the disk file and display it on the slide. Although this isn’t a difficult task, the fact that I have to do it really irks me. Solving the problem this way is slow, totally unscalable, and as far as I can tell, the only way to solve the problem.
Until recent versions of Microsoft Access, taking information in a data structure (like an ADO Recordset) and binding it to controls on a form was next to impossible, as well. Around Access 2000 or so, it became possible to bind controls on a form to an open Recordset, making it almost possible to create Access applications that retrieved data from middle-tier business objects. (The implementation isn’t perfect, but it’s better than nothing.)
The same issues come up when building .NET applications, although almost every method that requires a file name also accepts an in-memory data structure, or some type of stream, from which it can work with data.
Case in point: Recently, a friend was building an application, and he needed to be able to send an e-mail message to a list of recipients with a personalized attachment for each recipient. (No, he’s not building a SPAM engine-he’s simply building an application that can manage a little mailing list. Really.) The problem is that he didn’t want to save each personalized attachment to disk before sending the message, and every technique he found required a file name or a stream when specifying the attachment. He had neither-he had a String instance containing his text. He was well aware that saving each file to disk would cause a serious scalability problem, because he might, at some point, want to host this functionality on a Web site with multiple concurrent users.
There really are two issues here: how to send an e-mail at all from a client application, and once you can send an e-mail message at all, how you can add an attachment without first saving the attachment to disk.
Sending e-mail is simple: in the .NET Framework 2.0, you can use the System.Net.Mail namespace, and its MailMessage and Attachment classes to do the work. In the simplest case, you could write code like this to create a mail message, add an attachment, and send the message. You must fill in all the specific information, including the sender and recipient e-mail addresses, along with the name or IP address of your SMTP server. This code also assumes that you can use your current credentials to authenticate on the SMTP server (this code assumes that you have already added an Imports or using statement for the System.Net.Mail namespace):
[Visual Basic]
Dim msg As New MailMessage( _
"Sender@sender.com", _
"Recipient@recipient.com", _
"Subject text here", _
"Body text here")
msg.Attachments.Add( _
New Attachment("C:\Myattachment.txt"))
Dim client As _
New SmtpClient("Your SMTP Server")
client.UseDefaultCredentials = True
client.Send(msg)
[C#]
MailMessage msg =
new MailMessage(
"Sender@sender.com",
"Recipient@recipient.com",
"Subject text here",
"Body text here");
msg.Attachments.Add(
new Attachment(
"C:\\Myattachment.txt"));
SmtpClient client =
new SmtpClient("Your SMTP Server");
client.UseDefaultCredentials = true;
client.Send(msg);
If you can’t use your current credentials to authenticate on the SMTP server, you can use code like the following to supply the necessary credentials, rather than setting the UseDefaultCredentials property of the SmtpClient class to True. Supply the SmtpClient.Credentials property before calling the Send method:
[Visual Basic]
client.Credentials = _
New System.Net.NetworkCredential( _
"username", "password", "domain")
[C#]
client.Credentials =
new System.Net.NetworkCredential(
"username", "password", "domain");
This all works fine, and I’m guessing that if you have ever needed to send e-mail, you figured this all out. What if, as I mentioned earlier, you want to create the attachment “on the fly”? That is, you don’t want to attach a text file-instead, you want to take the contents of a string, and attach the contents as a text file without ever saving the file on disk? No problem, you can do that. The trick lies in the overloaded versions of the constructor for the Attachment class. The class provides six overloaded versions. Three versions accept a string (plus possibly other information), where the string represents the name of a file. The other three versions allow you to supply a stream instead-the only trick, then, is to get the contents of the string into a stream. This task isn’t as easy as you might think, however.
You can’t simply convert text from a string into a stream-you must work around the conversion. You can create a new MemoryStream instance, passing to the stream’s constructor the bytes you retrieve from the string itself. Of course, you can’t simply retrieve the bytes from the string-you must use an encoder to do this, and the System.Text class provides several different encoders that can do the work for you. You can use the UTF32Encoding class to do the work for you, using the default encoder (see the documentation for this class for more information on all its behaviors). The GetBytes method of an encoder instance retrieves all the bytes in a specified string, like this:
[Visual Basic]
Dim stream As New MemoryStream( _
UTF32Encoding.Default.GetBytes(data))
[C#]
MemoryStream stream = new MemoryStream(
UTF32Encoding.Default.GetBytes(data));
To make it easier for my friend, I created a helper procedure, which creates the stream from the string he supplies, and then adds the attachment to the mail message (this procedure assumes you have added using/Imports statements for the System.IO and System.Text namespaces):
[Visual Basic]
Private Sub AddAttachmentFromStream( _
ByVal message As MailMessage, _
ByVal data As String, _
ByVal attachmentName As String)
Dim stream As New MemoryStream( _
UTF32Encoding.Default.GetBytes(data))
' Rewind the stream.
stream.Position = 0
' Create a new attachment, and
' add the attachment to the supplied
' message.
Dim att As New Attachment( _
stream, attachmentName)
message.Attachments.Add(att)
End Sub
[C#]
private void AddAttachmentFromStream(
MailMessage message,
String data,
String attachmentName)
{
MemoryStream stream =
new MemoryStream(
UTF32Encoding.Default.
GetBytes(data));
// Rewind the stream.
stream.Position = 0;
// Create a new attachment, and
// add the attachment to the supplied
// message.
Attachment att = new Attachment(
stream, attachmentName);
message.Attachments.Add(att);
}
Given this procedure, my friend wrote a test procedure like this, to try out the AddAttachmentFromStream method (with all the confidential information changed, of course):
[Visual Basic]
Dim sw As New StringWriter
' Write your data into the buffer:
sw.WriteLine("This is some text")
sw.WriteLine( _
"Personalized for a recipient.")
Dim msg As New MailMessage( _
"someone@somewhere.com", _
"someone@elsewhere.com", _
"Attachments on the fly", _
"Check out the attachment!")
AddAttachmentFromStream(msg, _
sw.ToString, "Attachment.txt")
Dim client As _
New SmtpClient("MySMTPServer")
client.UseDefaultCredentials = True
client.Send(msg)
[C#]
StringWriter sw = new StringWriter();
MailMessage msg =
new MailMessage(
"someone@somewhere.com",
"someone@elsewhere.com",
"Attachments on the fly",
"Check out the attachment");
// Write your data into the buffer:
sw.WriteLine("This is some text");
sw.WriteLine(
"Personalized for a recipient.");
SmtpClient client =
new SmtpClient("MySMTPServer");
client.UseDefaultCredentials = true;
client.Send(msg);
To test it out, add the AddAttachmentFromStream method to a project, add the sample code (and modify the e-mail addresses and the SMTP server name), and give it a try. You should easily be able to generate an attachment in memory, without ever saving a text file to disk.
This isn’t brain surgery-it’s hardly even tricky. But it’s never obvious how to convert from a string in memory into a stream, and many methods require you to supply a stream (or a file name). In those cases, if you have a string and need a stream instead, remember the technique shown here; it doesn’t only apply to email attachments. Finally, repeat after me: “I won’t use this power for the forces of evil.” Don’t start your own spamming service-I wouldn’t want to be responsible for that! (And if you find a solution to the PowerPoint challenge, please let me know!)