#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);
}