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