SmtpClient and Locked File Attachments
- by Rick Strahl
Got a note a couple of days ago from a client using one of my generic routines that wraps SmtpClient. Apparently whenever a file has been attached to a message and emailed with SmtpClient the file remains locked after the message has been sent. Oddly this particular issue hasn’t cropped up before for me although these routines are in use in a number of applications I’ve built. The wrapper I use was built mainly to backfit an old pre-.NET 2.0 email client I built using Sockets to avoid the CDO nightmares of the .NET 1.x mail client. The current class retained the same class interface but now internally uses SmtpClient which holds a flat property interface that makes it less verbose to send off email messages. File attachments in this interface are handled by providing a comma delimited list for files in an Attachments string property which is then collected along with the other flat property settings and eventually passed on to SmtpClient in the form of a MailMessage structure. The jist of the code is something like this: /// <summary>
/// Fully self contained mail sending method. Sends an email message by connecting
/// and disconnecting from the email server.
/// </summary>
/// <returns>true or false</returns>
public bool SendMail()
{
if (!this.Connect())
return false;
try
{
// Create and configure the message
MailMessage msg = this.GetMessage();
smtp.Send(msg);
this.OnSendComplete(this);
}
catch (Exception ex)
{
string msg = ex.Message;
if (ex.InnerException != null)
msg = ex.InnerException.Message;
this.SetError(msg);
this.OnSendError(this);
return false;
}
finally
{
// close connection and clear out headers // SmtpClient instance nulled out
this.Close();
}
return true;
}
/// <summary>
/// Configures the message interface
/// </summary>
/// <param name="msg"></param>
protected virtual MailMessage GetMessage()
{
MailMessage msg = new MailMessage();
msg.Body = this.Message;
msg.Subject = this.Subject;
msg.From = new MailAddress(this.SenderEmail, this.SenderName);
if (!string.IsNullOrEmpty(this.ReplyTo))
msg.ReplyTo = new MailAddress(this.ReplyTo);
// Send all the different recipients
this.AssignMailAddresses(msg.To, this.Recipient);
this.AssignMailAddresses(msg.CC, this.CC);
this.AssignMailAddresses(msg.Bcc, this.BCC);
if (!string.IsNullOrEmpty(this.Attachments))
{
string[] files = this.Attachments.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string file in files)
{
msg.Attachments.Add(new Attachment(file));
}
}
if (this.ContentType.StartsWith("text/html"))
msg.IsBodyHtml = true;
else
msg.IsBodyHtml = false;
msg.BodyEncoding = this.Encoding;
… additional code omitted
return msg;
}
Basically this code collects all the property settings of the wrapper object and applies them to the SmtpClient and in GetMessage() to an individual MailMessage properties. Specifically notice that attachment filenames are converted from a comma-delimited string to filenames from which new attachments are created.
The code as it’s written however, will cause the problem with file attachments not being released properly. Internally .NET opens up stream handles and reads the files from disk to dump them into the email send stream. The attachments are always sent correctly but the local files are not immediately closed.
As you probably guessed the issue is simply that some resources are not automatcially disposed when sending is complete and sure enough the following code change fixes the problem:
// Create and configure the message
using (MailMessage msg = this.GetMessage())
{
smtp.Send(msg);
if (this.SendComplete != null)
this.OnSendComplete(this);
// or use an explicit msg.Dispose() here
}
The Message object requires an explicit call to Dispose() (or a using() block as I have here) to force the attachment files to get closed.
I think this is rather odd behavior for this scenario however. The code I use passes in filenames and my expectation of an API that accepts file names is that it uses the files by opening and streaming them and then closing them when done. Why keep the streams open and require an explicit .Dispose() by the calling code which is bound to lead to unexpected behavior just as my customer ran into? Any API level code should clean up as much as possible and this is clearly not happening here resulting in unexpected behavior. Apparently lots of other folks have run into this before as I found based on a few Twitter comments on this topic.
Odd to me too is that SmtpClient() doesn’t implement IDisposable – it’s only the MailMessage (and Attachments) that implement it and require it to clean up for left over resources like open file handles. This means that you couldn’t even use a using() statement around the SmtpClient code to resolve this – instead you’d have to wrap it around the message object which again is rather unexpected.
Well, chalk that one up to another small unexpected behavior that wasted a half an hour of my time – hopefully this post will help someone avoid this same half an hour of hunting and searching.
Resources:
Full code to SmptClientNative (West Wind Web Toolkit Repository)
SmtpClient Documentation MSDN
© Rick Strahl, West Wind Technologies, 2005-2010Posted in .NET