欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入Lumisoft.NET实现邮件发送功能的方法详解

程序员文章站 2024-03-02 23:34:46
在前面的一些文章中,有介绍过dotnet内置smtp类的邮件发送功能,附件、嵌入图片的模式都有介绍,本文继续介绍lumisoft.net这个非常优秀的开源组件,用该组件来设...

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

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

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

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

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

深入Lumisoft.NET实现邮件发送功能的方法详解 

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

深入Lumisoft.NET实现邮件发送功能的方法详解


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

深入Lumisoft.NET实现邮件发送功能的方法详解

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

 深入Lumisoft.NET实现邮件发送功能的方法详解

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

深入Lumisoft.NET实现邮件发送功能的方法详解

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

深入Lumisoft.NET实现邮件发送功能的方法详解

介绍完毕相关的功能效果图,下面我们来分析下主要功能实现的代码:

复制代码 代码如下:

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