#region License /* ************************************************************** * Author: Rick Strahl * © West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. ************************************************************** */ #endregion using System; using System.Text; using System.Threading; using System.IO; using System.Net.Mail; using System.Net; using System.Security; using System.Collections.Generic; namespace Westwind.InternetTools { /// /// SMTP Wrapper around System.Net.Email.SmtpClient. Provided /// here mainly to provide compatibility with existing wwSmtp code /// and to provide a slightly more user friendly front end interface /// on a single object. /// public class SmtpClientNative : IDisposable { /// /// Mail Server to send message through. Should be a domain name /// (mail.yourserver.net) or IP Address (211.123.123.123). /// /// You can also provide a port number as part of the string which will /// override the ServerPort (yourserver.net:211) /// Class wwSmtp /// public string MailServer = string.Empty; /// /// Port on the mail server to send through. Defaults to port 25. /// public int ServerPort = 25; /// /// Use Tls Security /// public bool UseSsl = false; /// /// Email address or addresses of the Recipient. Comma delimit multiple addresses. To have formatted names use /// "Rick Strahl" <rstrahl@west-wind.com> /// public string Recipient = string.Empty; /// /// Carbon Copy Recipients /// public string CC = string.Empty; /// /// Blind Copy Recipients /// public string BCC = string.Empty; /// /// Email address of the sender /// public string SenderEmail = string.Empty; /// /// Display name of the sender (optional) /// public string SenderName = String.Empty; /// /// The ReplyTo address /// public string ReplyTo = String.Empty; /// /// Message Subject. /// public string Subject = String.Empty; /// /// The body of the message. /// public string Message = String.Empty; /// /// Username to connect to the mail server. /// public string Username = String.Empty; /// /// Password to connect to the mail server. /// public string Password = String.Empty; /// /// Any attachments you'd like to send /// public string Attachments = String.Empty; /// /// The content type of the message. text/plain default or you can set to any other type like text/html /// public string ContentType = "text/plain"; /// /// Character Encoding for the message. /// public string CharacterEncoding = "8bit"; /// /// The character Encoding used to write the stream out to disk /// Defaults to the default Locale used on the server. /// public System.Text.Encoding Encoding = Encoding.Default; /// /// /// public string AlternateText = string.Empty; /// /// The content type for the alternate /// public string AlternateTextContentType = "text/plain"; /// /// The user agent for the x-mailer /// public string UserAgent = ""; /// /// Determines the priority of the message /// public string Priority = "Normal"; /// /// Determines whether a return receipt is sent /// public bool ReturnReceipt = false; /// /// /// /// protected internal List AlternateViews = new List(); /// /// An optional file name that appends logging information for the TCP/IP messaging /// to the specified file. /// public string LogFile = string.Empty; /// /// Determines whether wwSMTP passes back errors as exceptions or /// whether it sets error properties. Right now only error properties /// work reliably. /// public bool HandleExceptions = true; /// /// An Error Message if the result is negative or Error is set to true; /// public string ErrorMessage = string.Empty; /// /// Error Flag set when an error occurs. /// public bool Error = false; /// /// Connection timeouts for the mail server in seconds. If this timeout is exceeded waiting for a connection /// or for receiving or sending data the request is aborted and fails. /// public int Timeout = 30; /// /// SMTP headers for this email request /// public Dictionary Headers = new Dictionary(); /// /// Event that's fired after each message is sent. This /// event differs from SendComplete that it fires /// after each send operation of each message rather /// than before closing the connection. /// public event delSmtpNativeEvent MessageSendComplete; /// /// Event that's fired after each message is sent. This /// event differs from SendComplete that it fires /// after each send operation of each message rather /// than before closing the connection. /// public event delSmtpNativeEvent MessageSendError; /// /// Event fired when sending of a message or multiple messages /// is complete and the connection is to be closed. This event /// occurs only once per connection as opposed to the MessageSendComplete /// event which fires after each message is sent regardless of the /// number of SendMessage operations. /// public event delSmtpNativeEvent SendComplete; /// /// Event fired when an error occurs during processing and before /// the connection is closed down. /// public event delSmtpNativeEvent SendError; /// /// Internal instance of SmtpClient that holds the 'connection' /// effectively. /// private SmtpClient smtp = null; /// /// Adds an Smtp header to this email request. Headers are /// always cleared after a message has been sent or failed. /// /// /// public void AddHeader(string headerName, string value) { if (headerName.ToLower() == "clear" || headerName.ToLower() == "reset") this.Headers.Clear(); else { if (!Headers.ContainsKey(headerName)) this.Headers.Add(headerName, value); else this.Headers[headerName] = value; } } /// /// Adds headers from a CR/LF separate string that has key:value header pairs /// defined. /// /// public void AddHeadersFromString(string headers) { string[] lines = headers.Split(new char[2] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines) { string[] tokens = line.Split(':'); if (tokens.Length != 2) continue; this.AddHeader(tokens[0].Trim(), tokens[1].Trim()); } } /// /// Starts a new SMTP session. Note this doesn't actually open a connection /// but just configures and sets up the SMTP session. The actual connection /// is opened only when a message is actually sent /// /// public bool Connect() { //if (!string.IsNullOrEmpty(this.LogFile)) // this.LogString("\r\n*** Starting SMTP connection - " + DateTime.Now.ToString()); // Allow for server:Port syntax (west-wind.com:1212) int serverPort = this.ServerPort; string server = this.MailServer; // if there's a port we need to split the address string[] parts = server.Split(':'); if (parts.Length > 1) { server = parts[0]; serverPort = int.Parse(parts[1]); } if (server == null || server == string.Empty) { this.SetError("No Mail Server specified."); this.Headers.Clear(); return false; } smtp = null; try { smtp = new SmtpClient(server, serverPort); if (this.UseSsl) smtp.EnableSsl = true; } catch (SecurityException) { this.SetError("Unable to create SmptClient due to missing permissions. If you are using a port other than 25 for your email server, SmtpPermission has to be explicitly added in Medium Trust."); this.Headers.Clear(); return false; } // This is a Total Send Timeout not a Connection timeout! smtp.Timeout = this.Timeout * 1000; if (!string.IsNullOrEmpty(this.Username)) smtp.Credentials = new NetworkCredential(this.Username, this.Password); return true; } /// /// Cleans up and closes the connection /// /// public bool Close() { this.smtp = null; // clear all existing headers this.Headers.Clear(); return true; } /// /// Fully self contained mail sending method. Sends an email message by connecting /// and disconnecting from the email server. /// /// true or false public bool SendMail() { if (!this.Connect()) return false; try { // Create and configure the message using (MailMessage msg = this.GetMessage()) { smtp.Send(msg); if (this.SendComplete != null) this.SendComplete(this); } } catch (Exception ex) { string msg = ex.Message; if (ex.InnerException != null) msg = ex.InnerException.Message; this.SetError(msg); if (this.SendError != null) this.SendError(this); return false; } finally { // close connection and clear out headers this.Close(); } return true; } /// /// Run mail sending operation on a separate thread and asynchronously /// Operation does not return any information about completion. /// /// public void SendMailAsync() { //ThreadStart delSendMail = new ThreadStart(this.SendMailRun); //delSendMail.BeginInvoke(null, null); Thread mailThread = new Thread(this.SendMailRun); mailThread.Start(); } protected void SendMailRun() { // Create an extra reference to insure GC doesn't collect // the reference from the caller SmtpClientNative Email = this; Email.SendMail(); } /// /// Sends an individual message. Allows sending several messages /// on the same SMTP session without having to reconnect each time. /// /// This version assigns default properties assigned from the main /// mail object and allows overriding only of recipients /// /// Call after Connect() has been called and call Close() to /// close the connection afterwards /// /// public bool SendMessage(string recipient, string ccList, string bccList) { try { // Create and configure the message using (MailMessage msg = this.GetMessage()) { this.AssignMailAddresses(msg.To, recipient); this.AssignMailAddresses(msg.CC, ccList); this.AssignMailAddresses(msg.Bcc, bccList); smtp.Send(msg); } if (this.SendComplete != null) this.SendComplete(this); } catch (Exception ex) { this.SetError(ex.Message); if (this.SendError != null) this.SendError(this); return false; } return true; } /// /// Configures the message interface /// /// 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; msg.Priority = (MailPriority)Enum.Parse(typeof(MailPriority), this.Priority); if (!string.IsNullOrEmpty(this.ReplyTo)) msg.ReplyTo = new MailAddress(this.ReplyTo); if (this.ReturnReceipt) msg.DeliveryNotificationOptions = DeliveryNotificationOptions.OnSuccess; if (!string.IsNullOrEmpty(this.UserAgent)) this.AddHeader("x-mailer", this.UserAgent); if (!string.IsNullOrEmpty(this.AlternateText)) { byte[] alternateBytes = Encoding.Default.GetBytes(this.AlternateText); MemoryStream ms = new MemoryStream(alternateBytes); ms.Position = 0; msg.AlternateViews.Add(new AlternateView(ms)); //ms.Close(); } if (this.AlternateViews.Count > 0) { foreach (var view in this.AlternateViews) { msg.AlternateViews.Add(view); } } foreach (var header in this.Headers) { msg.Headers[header.Key] = header.Value; } return msg; } /// /// Assigns mail addresses from a string or comma delimited string list. /// Facilitates /// /// /// private void AssignMailAddresses(MailAddressCollection address, string recipients) { if (string.IsNullOrEmpty(recipients)) return; string[] recips = recipients.Split(',', ';'); for (int x = 0; x < recips.Length; x++) { address.Add(new MailAddress(recips[x])); } } /// /// Strips out just the email address from a full email address that might contain a display name /// in the format of: "Web Monitor" /// /// Full email address to parse. Note currently only "<" and ">" tags are recognized as message delimiters /// only the email address string GetEmailFromFullAddress(string fullEmail) { if (fullEmail.IndexOf("<") > 0) { int lnIndex = fullEmail.IndexOf("<"); int lnIndex2 = fullEmail.IndexOf(">"); string lcEmail = fullEmail.Substring(lnIndex + 1, lnIndex2 - lnIndex - 1); return lcEmail; } return fullEmail; } /// /// Adds a new Alternate view to the request. Passed from FoxPro /// which sets up this object. /// /// /// /// public void AddAlternateView(AlternateView view) { this.AlternateViews.Add(view); } /// /// Logs a message to the specified LogFile /// /// /// protected void LogString(string message) { if (string.IsNullOrEmpty(this.LogFile)) return; if (!message.EndsWith("\r\n")) message += "\r\n"; using (StreamWriter sw = new StreamWriter(this.LogFile, true)) { sw.Write(message); } } /// /// Internally used to set errors /// /// private void SetError(string errorMessage) { if (errorMessage == null || errorMessage.Length == 0) { this.ErrorMessage = string.Empty; this.Error = false; return; } ErrorMessage = errorMessage; Error = true; } #region IDisposable Members public void Dispose() { if (this.smtp != null) this.smtp = null; } #endregion } /// /// Delegate used to handle Completion and failure events /// /// public delegate void delSmtpNativeEvent(SmtpClientNative Smtp); }