Пример #1
0
        /// <summary>
        /// Sends a single mail message asyncronously.
        /// </summary>
        /// <remarks>The method raises events before and after sending, as well as on send failure.</remarks>
        /// <param name="mailMergeMessage">Mail merge message.</param>
        /// <param name="dataItem">The following types are accepted:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="AggregateException"></exception>
        public async Task SendAsync(MailMergeMessage mailMergeMessage, object dataItem)
        {
            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(SendAsync)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;

            try
            {
#if NET40
                await TaskEx.Run(async() =>
#else
                await Task.Run(async() =>
#endif
                {
                    var smtpClientConfig = Config.SmtpClientConfig[0];                     // use the standard configuration
                    using (var smtpClient = GetInitializedSmtpClient(smtpClientConfig))
                    {
                        await SendMimeMessageAsync(smtpClient, mailMergeMessage.GetMimeMessage(dataItem), smtpClientConfig).ConfigureAwait(false);
                        smtpClient.ProtocolLogger?.Dispose();
                    }
                }, _cancellationTokenSource.Token).ConfigureAwait(false);
            }
            finally
            {
                IsBusy = false;
            }
        }
Пример #2
0
        /// <summary>
        /// Sends mail messages syncronously to all recipients supplied in the data source
        /// of the mail merge message.
        /// </summary>
        /// <param name="mailMergeMessage">Mail merge message.</param>
        /// <param name="dataSource">IEnumerable data source with values for the placeholders of the MailMergeMessage.
        /// IEnumerable&lt;T&gt; where T can be the following types:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <remarks>
        /// In order to use a DataTable as a dataSource, use System.Data.DataSetExtensions and convert it with DataTable.AsEnumerable()
        /// </remarks>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="SmtpCommandException"></exception>
        /// <exception cref="SmtpProtocolException"></exception>
        /// <exception cref="AuthenticationException"></exception>
        public void Send <T>(MailMergeMessage mailMergeMessage, IEnumerable <T> dataSource)
        {
            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(Send)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;

            var sentMsgCount = 0;

            try
            {
                var dataSourceList = dataSource.ToList();

                var startTime    = DateTime.Now;
                var numOfRecords = dataSourceList.Count;

                var smtpClientConfig = Config.SmtpClientConfig[0];                 // use the standard configuration
                using (var smtpClient = GetInitializedSmtpClient(smtpClientConfig))
                {
                    OnMergeBegin?.Invoke(this, new MailSenderMergeBeginEventArgs(startTime, numOfRecords));

                    foreach (var dataItem in dataSourceList)
                    {
                        OnMergeProgress?.Invoke(this,
                                                new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, 0));

                        var mimeMessage = mailMergeMessage.GetMimeMessage(dataItem);
                        if (_cancellationTokenSource.IsCancellationRequested)
                        {
                            break;
                        }
                        SendMimeMessage(smtpClient, mimeMessage, smtpClientConfig);
                        sentMsgCount++;
                        if (_cancellationTokenSource.IsCancellationRequested)
                        {
                            break;
                        }

                        OnMergeProgress?.Invoke(this,
                                                new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, 0));

                        Thread.Sleep(smtpClientConfig.DelayBetweenMessages);
                        if (_cancellationTokenSource.IsCancellationRequested)
                        {
                            break;
                        }
                    }

                    OnMergeComplete?.Invoke(this,
                                            new MailSenderMergeCompleteEventArgs(startTime, DateTime.Now, numOfRecords, sentMsgCount, 0, 1));
                    smtpClient.ProtocolLogger?.Dispose();
                }
            }
            finally
            {
                IsBusy = false;
            }
        }
Пример #3
0
        /// <summary>
        /// Sends a single mail message asynchronously.
        /// </summary>
        /// <remarks>The method raises events before and after sending, as well as on send failure.</remarks>
        /// <param name="mailMergeMessage">Mail merge message.</param>
        /// <param name="dataItem">The following types are accepted:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="AggregateException"></exception>
        /// <exception cref="MailMergeMessage.MailMergeMessageException"></exception>
        public async Task SendAsync(MailMergeMessage mailMergeMessage, object dataItem)
        {
            if (mailMergeMessage == null)
            {
                throw new ArgumentNullException($"{nameof(SendAsync)}: {nameof(mailMergeMessage)} is null.");
            }

            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(SendAsync)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;

            try
            {
                await Task.Run(async() =>
                {
                    var smtpClientConfig = Config.SmtpClientConfig[0]; // use the standard configuration
                    using (var smtpClient = GetInitializedSmtpClientDelegate(smtpClientConfig))
                    {
                        MimeMessage mimeMessage = null;
                        try
                        {
                            mimeMessage = mailMergeMessage.GetMimeMessage(dataItem);
                        }
                        catch (Exception exception)
                        {
                            var mmFailureEventArgs = new MailMessageFailureEventArgs(exception, mailMergeMessage, dataItem, mimeMessage, true);
                            if (exception is MailMergeMessage.MailMergeMessageException ex)
                            {
                                mmFailureEventArgs = new MailMessageFailureEventArgs(ex, mailMergeMessage, dataItem,
                                                                                     ex.MimeMessage, true);
                            }

                            OnMessageFailure?.Invoke(this, mmFailureEventArgs);

                            // event delegate may have modified the mimeMessage and decided not to throw an exception
                            if (mmFailureEventArgs.ThrowException || mmFailureEventArgs.MimeMessage == null)
                            {
                                throw;
                            }

                            // set MimeMessage from OnMessageFailure delegate
                            mimeMessage = mmFailureEventArgs.MimeMessage;
                        }

                        await SendMimeMessageAsync(smtpClient, mimeMessage, smtpClientConfig).ConfigureAwait(false);
                        smtpClient.ProtocolLogger?.Dispose();
                        smtpClient.Disconnect(true, _cancellationTokenSource.Token);
                    }
                }, _cancellationTokenSource.Token).ConfigureAwait(false);
            }
            finally
            {
                RenewCancellationTokenSource();
                IsBusy = false;
            }
        }
Пример #4
0
 internal MailMessageFailureEventArgs(Exception error, MailMergeMessage mailMergeMessage, object dataSource, MimeMessage mimeMessage = null, bool throwException = true)
 {
     Error            = error;
     MailMergeMessage = mailMergeMessage;
     DataSource       = dataSource;
     MimeMessage      = mimeMessage;
     ThrowException   = throwException;
 }
Пример #5
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="mailMergeMessage">The parent MailMergeMessage, where HtmlBodyBuilder processes HtmlText and Subject properties.</param>
        /// <param name="dataItem"></param>
        public HtmlBodyBuilder(MailMergeMessage mailMergeMessage, object dataItem)
        {
            _docBaseUri            = new Uri(string.Concat(UriScheme.File, UriScheme.SchemeDelimiter));
            _mailMergeMessage      = mailMergeMessage;
            _dataItem              = dataItem;
            BinaryTransferEncoding = mailMergeMessage.Config.BinaryTransferEncoding;

            // Create a new parser front-end (can be re-used)
            var parser = new HtmlParser();

            //Just get the DOM representation
            _htmlDocument = parser.Parse(mailMergeMessage.HtmlText);
        }
Пример #6
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="mailMergeMessage">The parent MailMergeMessage, where HtmlBodyBuilder processes HtmlText and Subject properties.</param>
        /// <param name="dataItem"></param>
        public HtmlBodyBuilder(MailMergeMessage mailMergeMessage, object dataItem)
        {
            DocBaseUri             = mailMergeMessage.Config.FileBaseDirectory;
            _mailMergeMessage      = mailMergeMessage;
            _dataItem              = dataItem;
            BinaryTransferEncoding = mailMergeMessage.Config.BinaryTransferEncoding;

            // Create a new parser front-end (can be re-used)
            var parser = new HtmlParser();

            //Just get the DOM representation
            _htmlDocument = parser.Parse(mailMergeMessage.HtmlText);
        }
Пример #7
0
        /// <summary>
        /// Sends a single mail merge message.
        /// </summary>
        /// <param name="mailMergeMessage">Message to send.</param>
        /// <param name="dataItem">The following types are accepted:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="SmtpCommandException"></exception>
        /// <exception cref="SmtpProtocolException"></exception>
        /// <exception cref="AuthenticationException"></exception>
        /// <exception cref="MailMergeMessage.MailMergeMessageException"></exception>
        public void Send(MailMergeMessage mailMergeMessage, object dataItem)
        {
            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(Send)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;

            try
            {
                var smtpClientConfig = Config.SmtpClientConfig[0]; // use the standard configuration
                using (var smtpClient = GetInitializedSmtpClient(smtpClientConfig))
                {
                    MimeMessage mimeMessage = null;

                    try
                    {
                        mimeMessage = mailMergeMessage.GetMimeMessage(dataItem);
                    }
                    catch (Exception exception)
                    {
                        var mmFailureEventArgs = new MailMessageFailureEventArgs(exception, mailMergeMessage, dataItem, mimeMessage, true);
                        if (exception is MailMergeMessage.MailMergeMessageException ex)
                        {
                            mmFailureEventArgs = new MailMessageFailureEventArgs(ex, mailMergeMessage, dataItem,
                                                                                 ex.MimeMessage, true);
                        }

                        OnMessageFailure?.Invoke(this, mmFailureEventArgs);

                        // event delegate may have modified the mimeMessage and decided not to throw an exception
                        if (mmFailureEventArgs.ThrowException || mmFailureEventArgs.MimeMessage == null)
                        {
                            throw;
                        }

                        // set MimeMessage from OnMessageFailure delegate
                        mimeMessage = mmFailureEventArgs.MimeMessage;
                    }

                    SendMimeMessage(smtpClient, mimeMessage, smtpClientConfig);
                    smtpClient.ProtocolLogger?.Dispose();
                }
            }
            finally
            {
                IsBusy = false;
            }
        }
Пример #8
0
        /// <summary>
        /// Gets the MailAddress representation of the MailMergeAddress.
        /// </summary>
        /// <returns>Returns a MailAddress ready to be used for a MailAddress, or Null if no address part exists.</returns>
        /// <exception cref="NullReferenceException">Throws a NullReferenceException if TextVariableManager is null.</exception>
        /// <exception cref="FormatException">Throws a FormatException if the computed MailAddress is not valid.</exception>
        internal MailboxAddress GetMailAddress(MailMergeMessage mmm, object dataItem)
        {
            var address     = mmm.SearchAndReplaceVars(Address, dataItem);
            var displayName = mmm.SearchAndReplaceVars(DisplayName, dataItem);

            if (string.IsNullOrEmpty(displayName))
            {
                displayName = null;
            }

            // Exclude invalid address from further process
            if (!EmailValidator.Validate(address, false, true))
            {
                return(null);
            }

            return(displayName != null
                        ? new MailboxAddress(DisplayNameCharacterEncoding, displayName, address)
                        : new MailboxAddress(DisplayNameCharacterEncoding, address, address));
        }
Пример #9
0
        /// <summary>
        /// Sends a single mail merge message.
        /// </summary>
        /// <param name="mailMergeMessage">Message to send.</param>
        /// <param name="dataItem">The following types are accepted:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="SmtpCommandException"></exception>
        /// <exception cref="SmtpProtocolException"></exception>
        /// <exception cref="AuthenticationException"></exception>
        public void Send(MailMergeMessage mailMergeMessage, object dataItem)
        {
            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(Send)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;

            try
            {
                var smtpClientConfig = Config.SmtpClientConfig[0];                 // use the standard configuration
                using (var smtpClient = GetInitializedSmtpClient(smtpClientConfig))
                {
                    SendMimeMessage(smtpClient, mailMergeMessage.GetMimeMessage(dataItem), smtpClientConfig);
                    smtpClient.ProtocolLogger?.Dispose();
                }
            }
            finally
            {
                IsBusy = false;
            }
        }
Пример #10
0
        /// <summary>
        /// Sends mail messages asynchronously to all recipients supplied in the data source
        /// of the mail merge message.
        /// </summary>
        /// <param name="mailMergeMessage">Mail merge message.</param>
        /// <param name="dataSource">IEnumerable data source with values for the placeholders of the MailMergeMessage.
        /// IEnumerable&lt;T&gt; where T can be the following types:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <remarks>
        /// In order to use a DataTable as a dataSource, use System.Data.DataSetExtensions and convert it with DataTable.AsEnumerable()
        /// </remarks>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="Exception">The first exception found in one of the async tasks.</exception>
        /// <exception cref="MailMergeMessage.MailMergeMessageException"></exception>
        public async Task SendAsync <T>(MailMergeMessage mailMergeMessage, IEnumerable <T> dataSource)
        {
            if (mailMergeMessage == null)
            {
                throw new ArgumentNullException($"{nameof(SendAsync)}: {nameof(mailMergeMessage)} is null.");
            }

            if (dataSource == null)
            {
                throw new ArgumentNullException($"{nameof(dataSource)} is null.");
            }

            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(SendAsync)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;
            var sentMsgCount  = 0;
            var errorMsgCount = 0;

            var tasksUsed = new HashSet <int>();

            void AfterSend(object obj, MailSenderAfterSendEventArgs args)
            {
                if (args.Error == null)
                {
                    Interlocked.Increment(ref sentMsgCount);
                }
                else
                {
                    Interlocked.Increment(ref errorMsgCount);
                }
            }

            OnAfterSend += AfterSend;

            var startTime = DateTime.Now;

            var queue = new ConcurrentQueue <T>(dataSource);

            var numOfRecords = queue.Count;
            var sendTasks    = new Task[Config.MaxNumOfSmtpClients];

            // The max. number of configurations used is the number of parallel smtp clients
            var smtpConfigForTask = new SmtpClientConfig[Config.MaxNumOfSmtpClients];

            // Set as many smtp configs as we have for each task
            // Example: 5 tasks with 2 configs: task 0 => config 0, task 1 => config 1, task 2 => config 0, task 3 => config 1, task 4 => config 0, task 5 => config 1
            for (var i = 0; i < Config.MaxNumOfSmtpClients; i++)
            {
                smtpConfigForTask[i] = Config.SmtpClientConfig[i % Config.SmtpClientConfig.Length];
            }

            for (var i = 0; i < sendTasks.Length; i++)
            {
                var taskNo = i;
                sendTasks[taskNo] = Task.Run(async() =>
                {
                    using var smtpClient = GetInitializedSmtpClientDelegate(smtpConfigForTask[taskNo]);
                    while (queue.TryDequeue(out var dataItem))
                    {
                        lock (tasksUsed)
                        {
                            tasksUsed.Add(taskNo);
                        }

                        // Delay between messages is also the delay until the first message will be sent
                        await Task.Delay(smtpConfigForTask[taskNo].DelayBetweenMessages, _cancellationTokenSource.Token).ConfigureAwait(false);

                        var localDataItem       = dataItem; // no modified enclosure
                        MimeMessage mimeMessage = null;
                        try
                        {
                            mimeMessage = await Task.Run(() => mailMergeMessage.GetMimeMessage(localDataItem), _cancellationTokenSource.Token).ConfigureAwait(false);
                        }
                        catch (Exception exception)
                        {
                            OnMergeProgress?.Invoke(this,
                                                    new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, errorMsgCount));

                            var mmFailureEventArgs = new MailMessageFailureEventArgs(exception, mailMergeMessage, dataItem, mimeMessage, true);
                            if (exception is MailMergeMessage.MailMergeMessageException ex)
                            {
                                mmFailureEventArgs = new MailMessageFailureEventArgs(ex, mailMergeMessage, dataItem,
                                                                                     ex.MimeMessage, true);
                            }

                            OnMessageFailure?.Invoke(this, mmFailureEventArgs);

                            // event delegate may have modified the mimeMessage and decided not to throw an exception
                            if (mmFailureEventArgs.ThrowException || mmFailureEventArgs.MimeMessage == null)
                            {
                                Interlocked.Increment(ref errorMsgCount);

                                // Fire promised events
                                OnMergeProgress?.Invoke(this,
                                                        new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount,
                                                                                             errorMsgCount));
                                throw;
                            }

                            // set MimeMessage from OnMessageFailure delegate
                            mimeMessage = mmFailureEventArgs.MimeMessage;
                        }

                        OnMergeProgress?.Invoke(this,
                                                new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, errorMsgCount));

                        await SendMimeMessageAsync(smtpClient, mimeMessage, smtpConfigForTask[taskNo]).ConfigureAwait(false);

                        OnMergeProgress?.Invoke(this,
                                                new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, errorMsgCount));
                    }

                    smtpClient.ProtocolLogger?.Dispose();
                    smtpClient.Disconnect(true, _cancellationTokenSource.Token);
                }, _cancellationTokenSource.Token);
            }

            try
            {
                OnMergeBegin?.Invoke(this, new MailSenderMergeBeginEventArgs(startTime, numOfRecords));

                // Note await Task.WhenAll will only throw the FIRST exception of the aggregate exception!
                await Task.WhenAll(sendTasks.AsEnumerable()).ConfigureAwait(false);
            }
            finally
            {
                OnMergeComplete?.Invoke(this, new MailSenderMergeCompleteEventArgs(startTime, DateTime.Now, numOfRecords, sentMsgCount, errorMsgCount, tasksUsed.Count));

                OnAfterSend -= AfterSend;

                RenewCancellationTokenSource();
                IsBusy = false;
            }
        }
Пример #11
0
        /// <summary>
        /// This is the procedure taking care of sending the message (or saving to a file, respectively).
        /// </summary>
        /// <param name="smtpClient">The fully configures SmtpClient used to send the MimeMessate</param>
        /// <param name="mimeMsg">Mime message to send.</param>
        /// <param name="config"></param>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="SmtpCommandException"></exception>
        /// <exception cref="SmtpProtocolException"></exception>
        /// <exception cref="AuthenticationException"></exception>
        /// <exception cref="System.Net.Sockets.SocketException"></exception>
        internal void SendMimeMessage(SmtpClient smtpClient, MimeMessage mimeMsg, SmtpClientConfig config)
        {
            var       startTime = DateTime.Now;
            Exception sendException;

            // the client can rely on the sequence of events: OnBeforeSend, OnSendFailure (if any), OnAfterSend
            OnBeforeSend?.Invoke(smtpClient, new MailSenderBeforeSendEventArgs(config, mimeMsg, startTime, null, _cancellationTokenSource.Token.IsCancellationRequested));

            var failureCounter = 0;

            do
            {
                try
                {
                    sendException = null;
                    const string mailExt = ".eml";
                    switch (config.MessageOutput)
                    {
                    case MessageOutput.None:
                        break;

                    case MessageOutput.Directory:
                        mimeMsg.WriteTo(System.IO.Path.Combine(config.MailOutputDirectory, Guid.NewGuid().ToString("N") + mailExt), _cancellationTokenSource.Token);
                        break;

#if NETFRAMEWORK
                    case MessageOutput.PickupDirectoryFromIis:
                        // for requirements of message format see: https://technet.microsoft.com/en-us/library/bb124230(v=exchg.150).aspx
                        // and here http://www.vsysad.com/2014/01/iis-smtp-folders-and-domains-explained/
                        mimeMsg.WriteTo(System.IO.Path.Combine(config.MailOutputDirectory, Guid.NewGuid().ToString("N") + mailExt), _cancellationTokenSource.Token);
                        break;
#endif
                    default:
                        SendMimeMessageToSmtpServer(smtpClient, mimeMsg, config);
                        break;     // break switch
                    }
                    // when SendMimeMessageToSmtpServer throws less than _maxFailures exceptions,
                    // and succeeds after an exception, we MUST break the while loop here (else: infinite)
                    break;
                }
                catch (Exception ex)
                {
                    sendException = ex;
                    // exceptions which are thrown by SmtpClient:
                    if (ex is SmtpCommandException || ex is SmtpProtocolException ||
                        ex is AuthenticationException || ex is System.Net.Sockets.SocketException || ex is System.IO.IOException)
                    {
                        failureCounter++;
                        OnSendFailure?.Invoke(smtpClient,
                                              new MailSenderSendFailureEventArgs(sendException, failureCounter, config, mimeMsg));

                        Thread.Sleep(config.RetryDelayTime);

                        // on first SMTP failure switch to the backup configuration, if one exists
                        if (failureCounter == 1 && config.MaxFailures > 1)
                        {
                            var backupConfig = Config.SmtpClientConfig.FirstOrDefault(c => !c.Equals(config));
                            if (backupConfig == null)
                            {
                                continue;
                            }

                            backupConfig.MaxFailures = config.MaxFailures; // keep the logic within the current loop unchanged
                            config = backupConfig;
                            smtpClient.Disconnect(false);
                            smtpClient = GetInitializedSmtpClientDelegate(config);
                        }

                        if (failureCounter == config.MaxFailures && smtpClient.IsConnected)
                        {
                            smtpClient.Disconnect(false);
                        }
                    }
                    else
                    {
                        failureCounter = config.MaxFailures;
                        OnSendFailure?.Invoke(smtpClient, new MailSenderSendFailureEventArgs(sendException, 1, config, mimeMsg));
                    }
                }
            } while (failureCounter < config.MaxFailures && failureCounter > 0);

            OnAfterSend?.Invoke(smtpClient,
                                new MailSenderAfterSendEventArgs(config, mimeMsg, startTime, DateTime.Now, sendException, _cancellationTokenSource.Token.IsCancellationRequested));

            // Dispose the streams of file attachments and inline file attachments
            MailMergeMessage.DisposeFileStreams(mimeMsg);

            if (sendException != null)
            {
                throw sendException;
            }
        }
Пример #12
0
        /// <summary>
        /// Sends mail messages syncronously to all recipients supplied in the data source
        /// of the mail merge message.
        /// </summary>
        /// <param name="mailMergeMessage">Mail merge message.</param>
        /// <param name="dataSource">IEnumerable data source with values for the placeholders of the MailMergeMessage.
        /// IEnumerable&lt;T&gt; where T can be the following types:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <remarks>
        /// In order to use a DataTable as a dataSource, use System.Data.DataSetExtensions and convert it with DataTable.AsEnumerable()
        /// </remarks>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="SmtpCommandException"></exception>
        /// <exception cref="SmtpProtocolException"></exception>
        /// <exception cref="AuthenticationException"></exception>
        /// <exception cref="MailMergeMessage.MailMergeMessageException"></exception>
        public void Send <T>(MailMergeMessage mailMergeMessage, IEnumerable <T> dataSource)
        {
            if (mailMergeMessage == null)
            {
                throw new ArgumentNullException($"{nameof(Send)}: {nameof(mailMergeMessage)} is null.");
            }

            if (dataSource == null)
            {
                throw new ArgumentNullException($"{nameof(dataSource)} is null.");
            }

            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(Send)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;

            var sentMsgCount = 0;

            try
            {
                var dataSourceList = dataSource.ToList();

                var startTime    = DateTime.Now;
                var numOfRecords = dataSourceList.Count;

                var smtpClientConfig = Config.SmtpClientConfig[0]; // use the standard configuration
                using var smtpClient = GetInitializedSmtpClientDelegate(smtpClientConfig);
                OnMergeBegin?.Invoke(this, new MailSenderMergeBeginEventArgs(startTime, numOfRecords));

                foreach (var dataItem in dataSourceList)
                {
                    OnMergeProgress?.Invoke(this,
                                            new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, 0));

                    MimeMessage mimeMessage = null;
                    try
                    {
                        mimeMessage = mailMergeMessage.GetMimeMessage(dataItem);
                    }
                    catch (Exception exception)
                    {
                        var mmFailureEventArgs = new MailMessageFailureEventArgs(exception, mailMergeMessage, dataItem, mimeMessage, true);
                        if (exception is MailMergeMessage.MailMergeMessageException ex)
                        {
                            mmFailureEventArgs = new MailMessageFailureEventArgs(ex, mailMergeMessage, dataItem,
                                                                                 ex.MimeMessage, true);
                        }

                        OnMessageFailure?.Invoke(this, mmFailureEventArgs);

                        // event delegate may have modified the mimeMessage and decided not to throw an exception
                        if (mmFailureEventArgs.ThrowException || mmFailureEventArgs.MimeMessage == null)
                        {
                            // Invoke promised events
                            OnMergeProgress?.Invoke(this,
                                                    new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, 1));
                            smtpClient.Dispose();
                            OnMergeComplete?.Invoke(this,
                                                    new MailSenderMergeCompleteEventArgs(startTime, DateTime.Now, numOfRecords,
                                                                                         sentMsgCount, 1, 1));
                            throw;
                        }

                        // set MimeMessage from OnMessageFailure delegate
                        mimeMessage = mmFailureEventArgs.MimeMessage;
                    }

                    if (_cancellationTokenSource.IsCancellationRequested)
                    {
                        break;
                    }
                    SendMimeMessage(smtpClient, mimeMessage, smtpClientConfig);
                    sentMsgCount++;
                    if (_cancellationTokenSource.IsCancellationRequested)
                    {
                        break;
                    }

                    OnMergeProgress?.Invoke(this,
                                            new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, 0));

                    Thread.Sleep(smtpClientConfig.DelayBetweenMessages);
                    if (_cancellationTokenSource.IsCancellationRequested)
                    {
                        break;
                    }
                }

                smtpClient.ProtocolLogger?.Dispose();
                smtpClient.Disconnect(true); // fire OnSmtpDisconnected before OnMergeComplete
                smtpClient.Dispose();
                OnMergeComplete?.Invoke(this,
                                        new MailSenderMergeCompleteEventArgs(startTime, DateTime.Now, numOfRecords, sentMsgCount, 0, 1));
            }
            finally
            {
                RenewCancellationTokenSource();
                IsBusy = false;
            }
        }
Пример #13
0
 /// <summary>
 /// Constructor.
 /// </summary>
 internal MailMergeAddressCollection(MailMergeMessage msg)
 {
     _mailMergeMessage = msg;
 }
Пример #14
0
        /// <summary>
        /// Sends mail messages asynchronously to all recipients supplied in the data source
        /// of the mail merge message.
        /// </summary>
        /// <param name="mailMergeMessage">Mail merge message.</param>
        /// <param name="dataSource">IEnumerable data source with values for the placeholders of the MailMergeMessage.
        /// IEnumerable&lt;T&gt; where T can be the following types:
        /// Dictionary&lt;string,object&gt;, ExpandoObject, DataRow, class instances or anonymous types.
        /// The named placeholders can be the name of a Property, Field, or a parameterless method.
        /// They can also be chained together by using &quot;dot-notation&quot;.
        /// </param>
        /// <remarks>
        /// In order to use a DataTable as a dataSource, use System.Data.DataSetExtensions and convert it with DataTable.AsEnumerable()
        /// </remarks>
        /// <exception>
        /// If the SMTP transaction is the cause, SmtpFailedRecipientsException, SmtpFailedRecipientException or SmtpException can be expected.
        /// These exceptions throw after re-trying to send after failures (i.e. after MaxFailures * RetryDelayTime).
        /// </exception>
        /// <exception cref="InvalidOperationException">A send operation is pending.</exception>
        /// <exception cref="NullReferenceException"></exception>
        /// <exception cref="Exception">The first exception found in one of the async tasks.</exception>
        public async Task SendAsync <T>(MailMergeMessage mailMergeMessage, IEnumerable <T> dataSource)
        {
            if (mailMergeMessage == null || dataSource == null)
            {
                throw new NullReferenceException($"{nameof(mailMergeMessage)} and {nameof(dataSource)} must not be null.");
            }

            if (IsBusy)
            {
                throw new InvalidOperationException($"{nameof(SendAsync)}: A send operation is pending in this instance of {nameof(MailMergeSender)}.");
            }

            IsBusy = true;
            var sentMsgCount  = 0;
            var errorMsgCount = 0;

            var tasksUsed = new HashSet <int>();

            EventHandler <MailSenderAfterSendEventArgs> afterSend = (obj, args) =>
            {
                if (args.Error == null)
                {
                    Interlocked.Increment(ref sentMsgCount);
                }
                else
                {
                    Interlocked.Increment(ref errorMsgCount);
                }
            };

            OnAfterSend += afterSend;

            var startTime = DateTime.Now;

            var queue = new ConcurrentQueue <T>(dataSource);

            var numOfRecords = queue.Count;
            var sendTasks    = new Task[Config.MaxNumOfSmtpClients];

            // The max. number of configurations used is the number of parallel smtp clients
            var smtpConfigForTask = new SmtpClientConfig[Config.MaxNumOfSmtpClients];

            // Set as many smtp configs as we have for each task
            // Example: 5 tasks with 2 configs: task 0 => config 0, task 1 => config 1, task 2 => config 0, task 3 => config 1, task 4 => config 0, task 5 => config 1
            for (var i = 0; i < Config.MaxNumOfSmtpClients; i++)
            {
                smtpConfigForTask[i] = Config.SmtpClientConfig[i % Config.SmtpClientConfig.Length];
            }

            for (var i = 0; i < sendTasks.Length; i++)
            {
                var taskNo = i;
#if NET40
                sendTasks[taskNo] = TaskEx.Run(async() =>
#else
                sendTasks[taskNo] = Task.Run(async() =>
#endif
                {
                    using (var smtpClient = GetInitializedSmtpClient(smtpConfigForTask[taskNo]))
                    {
                        T dataItem;
                        while (queue.TryDequeue(out dataItem))
                        {
                            lock (tasksUsed)
                            {
                                tasksUsed.Add(taskNo);
                            }

                            var localDataItem = dataItem;                              // no modified enclosure
                            MimeMessage mimeMessage;
                            try
                            {
#if NET40
                                mimeMessage = await TaskEx.Run(() => mailMergeMessage.GetMimeMessage(localDataItem)).ConfigureAwait(false);
#else
                                mimeMessage = await Task.Run(() => mailMergeMessage.GetMimeMessage(localDataItem)).ConfigureAwait(false);
#endif
                            }
                            catch (Exception ex)
                            {
                                OnMessageFailure?.Invoke(this, new MailMessageFailureEventArgs(ex, mailMergeMessage, dataItem));
                                return;
                            }

                            OnMergeProgress?.Invoke(this,
                                                    new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, errorMsgCount));

                            /*
                             * if (OnMergeProgress != null)
                             *      Task.Factory.FromAsync((asyncCallback, obj) => OnMergeProgress.BeginInvoke(this, new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, errorMsgCount), asyncCallback, obj), OnMergeProgress.EndInvoke, null);
                             */
                            await SendMimeMessageAsync(smtpClient, mimeMessage, smtpConfigForTask[taskNo]).ConfigureAwait(false);

                            OnMergeProgress?.Invoke(this,
                                                    new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, errorMsgCount));
#if NET40
                            await TaskEx.Delay(smtpConfigForTask[taskNo].DelayBetweenMessages, _cancellationTokenSource.Token).ConfigureAwait(false);
#else
                            await Task.Delay(smtpConfigForTask[taskNo].DelayBetweenMessages, _cancellationTokenSource.Token).ConfigureAwait(false);
#endif
                        }

                        try
                        {
                            smtpClient.Disconnect(true);
                        }
                        catch (Exception)
                        {
                            // don't care for exception when disconnecting,
                            // because smptClient will be disposed immediately anyway
                        }
                        smtpClient.ProtocolLogger?.Dispose();
                    }
                }, _cancellationTokenSource.Token);
            }

            try
            {
                OnMergeBegin?.Invoke(this, new MailSenderMergeBeginEventArgs(startTime, numOfRecords));

                // Note await Task.WhenAll will only throw the FIRST exception of the aggregate exception!
#if NET40
                await TaskEx.WhenAll(sendTasks.AsEnumerable()).ConfigureAwait(false);
#else
                await Task.WhenAll(sendTasks.AsEnumerable()).ConfigureAwait(false);
#endif
            }
            finally
            {
                OnMergeComplete?.Invoke(this, new MailSenderMergeCompleteEventArgs(startTime, DateTime.Now, numOfRecords, sentMsgCount, errorMsgCount, tasksUsed.Count));

                OnAfterSend -= afterSend;

                IsBusy = false;
            }
        }
Пример #15
0
 internal MailMessageFailureEventArgs(Exception error, MailMergeMessage mailMergeMessage, object dataSource)
 {
     Error            = error;
     MailMergeMessage = mailMergeMessage;
     DataSource       = dataSource;
 }
Пример #16
0
 /// <summary>
 /// CTOR.
 /// Create an instance which loads the Formatters and Source extensions required by MailMergeLib.
 /// Error actions are SmartFormatters defaults.
 /// </summary>
 /// <param name="mailMergeMessage"></param>
 internal MailSmartFormatter(MailMergeMessage mailMergeMessage) : this()
 {
     ErrorAction        = mailMergeMessage.Config.SmartFormatterConfig.FormatErrorAction;
     Parser.ErrorAction = mailMergeMessage.Config.SmartFormatterConfig.ParseErrorAction;
 }