发起的. NET Core 开源组织号召,进展的速度是我自己也没有想到的,很多园友都积极参与(虽然有些人诚心砸场子,要是以我以前的宝脾气,这会应该被我打住院了吧,不过幸好是少数,做一件事总有人说好,也有人说是用武汉话说 "闹眼子"),.NET 社区不是没有乐于共享知识的人,只是没有一个完整和良好的生态环境,总之希望国内的. NET 发展越来越强大。我在这里想到一句话 "我们希望自己可以做巨浪,但我们也甘愿做巨浪来袭前的小浪"。
上面扯淡完毕(我这人干正事前,都要将一些扯淡的话,这个习惯改不掉了...)
项目中为了及时的通信,有直接发数据到页面,也有利用短信通知,也有我门今天介绍的邮件组件。我们今天的主要任务就是讲解一下有一个. NET 的免费开源的邮件组件 MailKit。本文将一如既往的结合实例和组件底层代码讲解一下相关组件的知识。(项目招人的时候,我都会问一下. NET 的底层原理,有一个大神问我这样有什么意义吗?我们也写不出. NET 底层那样的优秀处理方式,为何取了解这些,其实我个人觉得,问底层的原理,只是向为了跟好的处理一些程序出现的问题,以及对程序编码的时候,选择最合适的方式提升性能,任何一种方式都有优势和劣势,.NET 的类库代码也是如此,如果我们知道. NET 的底层实现,我们在项目的需求实现时,可以根据. NET 底层实现,选择合适的方式,以求性能最优)。
项目中使用 Email 的操作机会比较多,一般稍微大一点的项目,都会使用到邮件操作这一个操作。对于. NET 邮件操作的组件和方式比较多,今天我们就介绍一款邮件操作的组件 MailKit,这个邮件组件是一个开源免费的,我们现在就来了解一下这一个组件的特点。MimeKit 旨在通过尽可能接近地遵循 MIME 规范来解决这个问题,同时还为程序员提供了一个非常容易使用的高级 API。
组件的支持的客户端类型比较多,例如 SMTP 客户端、POP3 客户端、IMAP 客户端。该组件是一个跨平台的 Email 组件,该组件支持. NET 4.0,.NET 4.5,Xamarin.Android,Xamarin.iOS,Windows Phone 8.1 等等平台。该组件提供了一个 MIME 解析器,组件具备的解析特性灵活、性能高、很好的处理各种各样的破碎的 MIME 格式化。MimeKit 的性能实际上与 GMime 相当。
该组件在安全性的还是比较高的,处理安全的方式较多,SASL 认证、支持 S / MIME v3.2、支持 OpenPGP、支持 DKIM 签名等等方式。Mailkit 组件可以通过 CancellationToken 取消对应的操作,CancellationToken 传播应取消操作的通知,一个的 CancellationToken 使线程,线程池工作项目之间,或取消合作任务的对象。过实例化 CancellationTokenSource 对象来创建取消令牌,该对象管理从其 CancellationTokenSource.Token 属性检索的取消令牌。然后,将取消令牌传递到应该收到取消通知的任意数量的线程,任务或操作。令牌不能用于启动取消。
MailKit 组件支持异步操作,在内部编写的有关 I/O 异步操作的类。
上面介绍了 MailKit 组件的背景和特点,这里就介绍一下 Email 组件的简单应用。
- public void SentEmail(string path)
- {
- var message = new MimeMessage();
- //获取From标头中的地址列表,添加指定的地址
- message.From.Add(new MailboxAddress("Joey", "joey@friends.com"));
- //获取To头中的地址列表,添加指定的地址
- message.To.Add(new MailboxAddress("Alice", "alice@wonderland.com"));
- //获取或设置消息的主题
- message.Subject = "How you doin?";
- // 创建我们的消息文本,就像以前一样(除了不设置为message.Body)
- var body = new TextPart("plain")
- {
- Text = @"Hey Alice-- Joey"
- };
- // 为位于路径的文件创建图像附件
- var attachment = new MimePart("image", "gif")
- {
- ContentObject = new ContentObject(File.OpenRead(path), ContentEncoding.Default),
- ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
- ContentTransferEncoding = ContentEncoding.Base64,
- FileName = Path.GetFileName(path)
- };
- // 现在创建multipart / mixed容器来保存消息文本和图像附件
- var multipart = new Multipart("mixed")
- {
- body, attachment
- };
- // 现在将multipart / mixed设置为消息正文
- message.Body = multipart;
- }
调用该组件发送邮件和为邮件添加附件是比较简单的,第一步是实例化 MimeMessage 对象,对于该对象的解析将在下面进行,得到 MimeMessage 对象后,指定邮件的地址和主题等等相关信息。第二步实例化 TextPart 对象,为对象设定文本信息。若需要问邮件创建文件的附件,可以使用 MimePart 对象,包含内容(如消息正文文本或)的叶节点 MIME 部分一个附件。第四步为创建的邮件主体和文本以及附件信息后,可以创建 Multipart 对象,创建邮件容器,用来装载文本信息和附件。最后调用 MimeMessage.body 属性获取或设置消息的正文。
- var message = MimeMessage.Load(stream);
邮件的信息我们需要进行对应的解析,这里我们使用 MimeMessage 的 Load 方法,该方法从指定的流加载 MimeKit.MimeMessage。另一个加载数据的方式,可以使用 MimeParser 类,这里就不再解析了。
- public static void HandleMimeEntity(MimeEntity entity)
- {
- //MimeEntity转化为Multipart实体
- var multipart = entity as Multipart;
- if (multipart != null)
- {
- for (int i = 0; i < multipart.Count; i++)
- HandleMimeEntity(multipart[i]);
- return;
- }
- var rfc822 = entity as MessagePart;
- if (rfc822 != null)
- {
- var message = rfc822.Message;
- HandleMimeEntity(message.Body);
- return;
- }
- var part = (MimePart)entity;
- }
以上是对接收到的消息的一个遍历,采用递归遍历 MIME 结构。MIME 是内容的树结构,很像一个文件系统。MIME 确实定义了一组通用规则,用于邮件客户端如何解释 MIME 部分的树结构。的 内容处置头是为了给接收客户端提供提示以哪些部分是为了显示作为消息体的一部分,并且意在被解释为附件。另外两种方式这离就不做介绍了。
上面介绍了 Email 的基本操作就不做过多的介绍,在使用该组件时,较为的简单。这里就来看看该组件的类型结构和一些核心对象。类库结构有如下图:
- public static MimeMessage Load (ParserOptions options, Stream stream, bool persistent,
- CancellationToken cancellationToken = default (CancellationToken))
- {
- if (options == null)
- throw new ArgumentNullException (nameof (options));
- if (stream == null)
- throw new ArgumentNullException (nameof (stream));
- var parser = new MimeParser (options, stream, MimeFormat.Entity, persistent);
- return parser.ParseMessage (cancellationToken);
- }
该方法从指定的流加载 MimeMessage,具有 6 个方法重载。该方法返回一个 MimeMessage 对象,有源码可以看出,在该方法内部创建了一个 MimeParser 对象,MimeParser 包含内容(例如邮件正文文本或附件)的叶节点 MIME 部分。调用 ParseMessage 方法,解析来自流的消息。
2.TextPart.Text:
- public string Text {
- get {
- if (ContentObject == null)
- return string.Empty;
- var charset = ContentType.Parameters["charset"];
- using (var memory = new MemoryStream ()) {
- ContentObject.DecodeTo (memory);
- var content = memory.ToArray ();
- Encoding encoding = null;
- if (charset != null) {
- try {
- encoding = CharsetUtils.GetEncoding (charset);
- } catch (NotSupportedException) {
- }
- }
- if (encoding == null) {
- try {
- return CharsetUtils.UTF8.GetString (content, 0, (int) memory.Length);
- } catch (DecoderFallbackException) {
- encoding = CharsetUtils.Latin1;
- }
- }
- return encoding.GetString (content, 0, (int) memory.Length);
- }
- }
- set {
- SetText (Encoding.UTF8, value);
- }
- }
该属性获取解码的文本内容。该属性是一个可读可写的属性。ContentType.Parameters["charset"] 用于获取 charset 参数的值。该方法用来将参数的值设置为数据流并设置对应的编码。看到这里的异常处理结构,就想简单的谈几句,.NET 的异常比较的薄弱,很多时候在写. NET 的异常时就更加的简单,以上是对异常知识捕获,有些地方并没有做处理,有些地方是对异常的地方进行恢复。
- public virtual void WriteTo (FormatOptions options, Stream stream, bool contentOnly,
- CancellationToken cancellationToken = default (CancellationToken))
- {
- if (options == null)
- throw new ArgumentNullException (nameof (options));
- if (stream == null)
- throw new ArgumentNullException (nameof (stream));
- if (!contentOnly)
- Headers.WriteTo (options, stream, cancellationToken);
- }
该方法将 MimeEntity 写入到指定的数据流中,该方法接受参数 options 格式选项。stream 输出数据流,contentOnly 判断是否可写。该方法定义为虚方法,在继承此方法后,可以在子类种对该方法进行重写。
本人觉得在项目开发中,如果引入了第三方组件,我们尽量引入组件的源码,这样我们对整个组件的结构有一个认识,组件的实现方式我们也可以进行细致了解,尤其是我们在进行调试的事后更加有用,我们可以逐一的进行断点调试。以上是对该组件的一个简单介绍,有兴趣的可以去深入的了解和学习。
来源: http://www.cnblogs.com/pengze0902/p/6562447.html