视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
深入Lumisoft.NET实现邮件发送功能的方法详解
2020-11-27 22:41:00 责编:小采
文档


在前面的一些文章中,有介绍过DotNet内置SMTP类的邮件发送功能,附件、嵌入图片的模式都有介绍,本文继续介绍Lumisoft.NET这个非常优秀的开源组件,用该组件来设计开发邮件工具,将变得更加方便,功能更加强大。网上很多文章基本介绍如何使用该组件来收取邮件较多,较少介绍使用该组件做邮件发送功能的。本文主要探寻使用该组件实现邮件的发送功能,邮件发送有两种方式,一种是不用发件人即可发送邮件,一种是使用发件人账户密码和SMTP服务器来实现邮件发送的,本文分别对这两种方式进行介绍。

组件下载地址:http://www.lumisoft.ee/lswww/download/downloads/ 

组件论坛地址:http://www.lumisoft.ee/Forum/default.aspx?g=forum 

秉承一贯的做法,先贴出相关的实现图形,感官认识下,在进入详细的介绍说明,以求达到最好的理解深度。

1、 首先是发件人的设置,可以从文本文件的导出,以及新建等操作,以方便用户操作。 

 

2、 内容也支持导入导出,并且保存到数据库,方便进行记录及操作等,另外可以对内容进行随机混淆,混淆的内容在HTML邮件中式隐藏的,方便糊弄一下服务器的识别。



3、 邮件发送可以选择两种方式,下面将分别介绍这两种方式的实现,一种采用该控件封装非常好的邮件直投技术,不需要SMTP账号发送的;一种是普通的SMTP发送方式。当然我们还可以设置更多的参数,例如邮件尾部信息、HTML内容提示、 以及一些发送期间自动拨号的设置操作等。

4、 邮件直投技术,通过模拟账户来附加用户的邮件地址(或者可以成为伪装)。其中我填写了一些常用的SMTP服务器的域名,方便在其中构造合乎要求的邮件格式,还可以设置邮件回执通知,如下图所示。 

 

5、 如果是采用普通发送方式,那么就需要制定用户的账号密码等信息,发送的时候,自动从启动获取发件人信息进行批量发送操作。

6、 最后体验一下少量邮件的发送效果,发送采用多线程发送,多线程采用比较有名的SmartThreadPool组件,并且发送过程总详细记录其中的日志,供参考。

介绍完毕相关的功能效果图,下面我们来分析下主要功能实现的代码:
代码如下:
private TimerHelper timer = null;

        private void btnSend_Click(object sender, EventArgs e)
        {
            //重置计数变量
            failedItems = 0;
            successItems = 0;

            workItemsCompleted = 0;
            workItemsGenerated = 0;

            Portal.gc.FailedCount = 0;//重置失败次数

            STPStartInfo stpStartInfo = new STPStartInfo();
            stpStartInfo.IdleTimeout = 10;
            stpStartInfo.MaxWorkerThreads = 100;
            stpStartInfo.MinWorkerThreads = 0;
            //stpStartInfo.StartSuspended = true;
            _smartThreadPool = new SmartThreadPool(stpStartInfo);
            _workItemsGroup = _smartThreadPool;

            workItemsProducerThread = new Thread(new ThreadStart(this.WorkItemsProducer));
            workItemsProducerThread.IsBackground = true;
            workItemsProducerThread.Start();

            RefreshStatusCount();

            int intervalRedial = SystemConfig.Default.IntervalRedial * 1000 * 60;
            if (intervalRedial > 0)
            {
                if (timer != null)
                {
                    timer.Stop();
                    timer.Dispose();
                }
                timer = new TimerHelper(intervalRedial,false);
                timer.Execute += new TimerHelper.TimerExecution(timer_Execute);
                timer.Start();
            }
        }

        private static object locker = new object();
        private void timer_Execute()
        {
            if (Monitor.TryEnter(locker))
            {
                string message = string.Format("在时间 {0} 时刻执行了一次重拨号操作!", DateTime.Now);
                ShowSendStatus(message);

                string RasName = SystemConfig.Default.RasName;
                if (!string.IsNullOrEmpty(RasName))
                {
                    message = string.Format("正在准备重新拨号({0})", RasName);
                    ShowSendStatus(message);

                    Portal.gc.ReConnect(RasName);
                    Portal.gc.FailedCount = 0;//重新归零
                }

                Monitor.Exit(locker);
            }
            else
            {
                Monitor.Enter(locker);
                Monitor.Exit(locker);
            }
        }

上面是主要的任务生成操作以及相关的拨号操作,其中任务详细的生成代码如下所示。
private void WorkItemsProducer()
代码如下:
{
            CallCtrlWithThreadSafetyEx.SetText(this.txtSendDetail, "");

            EnableControl(false, true, true);
            string message = string.Format("任务开始");
            RecordMessage(message);

            #region 生成任务

            IWorkItemsGroup workItemsGroup = _workItemsGroup;
            if (null == workItemsGroup)
            {
                return;
            }

            List<string> addressList = GetAddressList();
            List<MyMailInfo> mailInfoList = GetMailInfo();
            for (int i = 0; i < addressList.Count; i++)
            {
                try
                {
                    SendJobInfo jobInfo = new SendJobInfo();
                    jobInfo.domainList = mailDomainList;
                    jobInfo.mailTo = addressList[i];
                    jobInfo.mailInfo = GetOneMail(mailInfoList, i);
                    jobInfo.ShowSendStatus = ShowSendStatus;
                    jobInfo.currentDomain = (i % mailDomainList.Count);//设置一个标志,默认那个账户开始发送
                    jobInfo.UseDirectSendType = SystemConfig.Default.EmailDirectSend;

                    //如果用户未指定发送账号,那么采用默认的显示名称
                    //如果为空,发送的时候,会自动采用邮件地址作为显示名称
                    if (string.IsNullOrEmpty(SystemConfig.Default.UserEmailFrom))
                    {
                        jobInfo.mailFromDisplay = SystemConfig.Default.DefaultFromDisplayName;
                    }

                    workItemCallback = new WorkItemCallback(this.DoWork);
                    workItemsGroup.QueueWorkItem(workItemCallback, jobInfo);
                    Thread.Sleep(100);
                }
                catch (ObjectDisposedException ex)
                {
                    LogTextHelper.WriteLine(ex.ToString());
                    continue;
                }

                Interlocked.Increment(ref workItemsGenerated);
            }

            #endregion

            RefreshStatusCount();
            message = string.Format("共有 {0} 个任务,还剩下 {1} 个",
                workItemsGenerated, workItemsGenerated - workItemsCompleted);
            CallCtrlWithThreadSafetyEx.SetText(this, message);
            RecordMessage(message);

            try
            {
                //workItemsGroup.Start();
                workItemsGroup.WaitForIdle();
                _smartThreadPool.Shutdown();
            }
            catch (Exception ex)
            {
                LogTextHelper.WriteLine(ex.ToString());
            }

            UpdateFinishStatus();
        }

由于采用了多线程来处理,所以停止发送的时候,需要把相关的线程对象进行释放,如下代码所示。
代码如下:
private void btnStop_Click(object sender, EventArgs e)
        {
            try
            {
                _smartThreadPool.Shutdown();
                _smartThreadPool.Dispose();
                _smartThreadPool = null;

                if (timer != null)
                {
                    timer.Stop();
                    timer.Dispose();
                }
            }
            catch (Exception ex)
            {
                LogTextHelper.WriteLine(ex.ToString());
            }

            UpdateFinishStatus();
        }

其中具体的邮件发送功能封装在SendJobInfo中,通过判断不同的类型,进行不同的发送操作。

其中最为关键的发送代码,就是如何利用LumiSoft.NET组件来构造相应的邮件对象,下面先先介绍下邮件直投的发送方式,由于该组件封装比较好,直投发送方式很简单:
代码如下:
Mail_Message message = Create_PlainText_Html_Attachment_Image(mailTo, mailFrom, mailFromDisplay);
SMTP_Client.QuickSend(message);

其中Create_PlainText_Html_Attachment_Image的封装函数详细内容如下所示:
代码如下:
代码

       private Mail_Message Create_PlainText_Html_Attachment_Image(string mailTo, string mailFrom, string mailFromDisplay)
        {
            Mail_Message msg = new Mail_Message();
            msg.MimeVersion = "1.0";
            msg.MessageID = MIME_Utils.CreateMessageID();
            msg.Date = DateTime.Now;
            msg.From = new Mail_t_MailboxList();
            msg.From.Add(new Mail_t_Mailbox(mailFromDisplay, mailFrom));
            msg.To = new Mail_t_AddressList();
            msg.To.Add(new Mail_t_Mailbox(mailTo, mailTo));
            msg.Subject = mailInfo.Title;

            //设置回执通知
            string notifyEmail = SystemConfig.Default.DispositionNotificationTo;
            if (!string.IsNullOrEmpty(notifyEmail) && ValidateUtil.IsEmail(notifyEmail))
            {
                msg.DispositionNotificationTo = new Mail_t_Mailbox(notifyEmail, notifyEmail);
            }

            #region MyRegion
            //--- multipart/mixed -----------------------------------
            MIME_h_ContentType contentType_multipartMixed = new MIME_h_ContentType(MIME_MediaTypes.Multipart.mixed);
            contentType_multipartMixed.Param_Boundary = Guid.NewGuid().ToString().Replace('-', '.');
            MIME_b_MultipartMixed multipartMixed = new MIME_b_MultipartMixed(contentType_multipartMixed);
            msg.Body = multipartMixed;

            //--- multipart/alternative -----------------------------
            MIME_Entity entity_multipartAlternative = new MIME_Entity();
            MIME_h_ContentType contentType_multipartAlternative = new MIME_h_ContentType(MIME_MediaTypes.Multipart.alternative);
            contentType_multipartAlternative.Param_Boundary = Guid.NewGuid().ToString().Replace('-', '.');
            MIME_b_MultipartAlternative multipartAlternative = new MIME_b_MultipartAlternative(contentType_multipartAlternative);
            entity_multipartAlternative.Body = multipartAlternative;
            multipartMixed.BodyParts.Add(entity_multipartAlternative);

            //--- text/plain ----------------------------------------
            MIME_Entity entity_text_plain = new MIME_Entity();
            MIME_b_Text text_plain = new MIME_b_Text(MIME_MediaTypes.Text.plain);
            entity_text_plain.Body = text_plain;

            //普通文本邮件内容,如果对方的收件客户端不支持HTML,这是必需的
            string plainTextBody = "如果你邮件客户端不支持HTML格式,或者你切换到“普通文本”视图,将看到此内容";
            if (!string.IsNullOrEmpty(SystemConfig.Default.PlaintTextTips))
            {
                plainTextBody = SystemConfig.Default.PlaintTextTips;
            }

            text_plain.SetText(MIME_TransferEncodings.QuotedPrintable, Encoding.UTF8, plainTextBody);
            multipartAlternative.BodyParts.Add(entity_text_plain);

            //--- text/html -----------------------------------------
            string htmlText = mailInfo.Content;//"<html>这是一份测试邮件,<img src=\"cid:test.jpg\">来自<font color=red><b>LumiSoft.Net</b></font></html>";
            MIME_Entity entity_text_html = new MIME_Entity();
            MIME_b_Text text_html = new MIME_b_Text(MIME_MediaTypes.Text.html);
            entity_text_html.Body = text_html;
            text_html.SetText(MIME_TransferEncodings.QuotedPrintable, Encoding.UTF8, htmlText);
            multipartAlternative.BodyParts.Add(entity_text_html);

            //--- application/octet-stream -------------------------
            foreach (string attach in mailInfo.Attachments)
            {
                multipartMixed.BodyParts.Add(Mail_Message.CreateAttachment(attach));
            }

            foreach (string imageFile in mailInfo.EmbedImages)
            {
                MIME_Entity entity_image = new MIME_Entity();
                entity_image.ContentDisposition = new MIME_h_ContentDisposition(MIME_DispositionTypes.Inline);
                string fileName = DirectoryUtil.GetFileName(imageFile, true);
                entity_image.ContentID = BytesTools.BytesToHex(Encoding.Default.GetBytes(fileName));               
                MIME_b_Image body_image = new MIME_b_Image(MIME_MediaTypes.Image.jpeg);
                entity_image.Body = body_image;
                body_image.SetDataFromFile(imageFile, MIME_TransferEncodings.Base);
                multipartMixed.BodyParts.Add(entity_image);
            }

            #endregion

            return msg;
        }

如果使用普通的账号方式发送SMTP邮件,主要代码如下所示,其中可以看出是利用了命令方式一步步和服务器进行交互的。
代码如下:
using (SMTP_Client client = new SMTP_Client())
                    {
                        int port = domainInfo.Ssl ? WellKnownPorts.SMTP_SSL : WellKnownPorts.SMTP;
                        if (domainInfo.Port > 0)
                        {
                            port = domainInfo.Port;
                        }

                        client.Connect(domainInfo.SmtpServer, port, domainInfo.Ssl);
                        client.Authenticate(domainInfo.Username, domainInfo.Password);
                        //string text = client.GreetingText;
                        client.MailFrom(mailFrom, -1);
                        client.RcptTo(mailTo);

                        MemoryStream stream = Create_Html_Attachment_Image(mailTo, mailFrom, mailFromDisplay);
                        client.SendMessage(stream);
                        client.Disconnect();
                    }

其中构造邮件内容的代码和刚才的部分类似,详细代码如下所示。
代码如下:
private MemoryStream Create_Html_Attachment_Image(string mailTo, string mailFrom, string mailFromDisplay)
        {
            Mime m = new Mime();
            MimeEntity mainEntity = m.MainEntity;

            mainEntity.From = new AddressList();
            mainEntity.From.Add(new MailboxAddress(mailFromDisplay, mailFrom));

            mainEntity.To = new AddressList();
            mainEntity.To.Add(new MailboxAddress(mailTo, mailTo));
            mainEntity.Subject = mailInfo.Title;
            mainEntity.ContentType = MediaType_enum.Multipart_mixed;

            MimeEntity textEntity = mainEntity.ChildEntities.Add();
            textEntity.ContentType = MediaType_enum.Text_html;
            textEntity.ContentTransferEncoding = ContentTransferEncoding_enum.QuotedPrintable;
            textEntity.DataText = mailInfo.Content;
.........................        

        MemoryStream msg = new MemoryStream();
            m.ToStream(msg);
            msg.Position = 0;

            return msg;
        }

利用Lumisoft.NET这个组件,可以实现很多相关的邮件操作,这里介于兴趣及篇幅原因,主要介绍邮件发送的功能模块,其中贴出的代码,一个是为了和感兴趣的朋友相互交流,一个也是为了自己今后做一个借鉴,并不鼓励大家用此软件或者代码来大批量发送垃圾邮件。

下载本文
显示全文
专题