程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#郵件發送相關問題(一)

C#郵件發送相關問題(一)

編輯:C#入門知識

郵件發送需考慮很多因素,包括發送郵件客戶端(一般編碼實現),發送和接收郵件服務器設置等。如果使用第三方郵件服務器作為發送服務器,就需要考慮該服務器的發送限制,(如發送郵件時間間隔,單位時間內發送郵件數量,是否使用安全連接SSL),同時無論使用第三方還是自己的郵件服務器都還需要考慮接收郵件服務器的限制。為理清思路,下面我們簡單回顧電子郵件系統的基本網絡結構和郵件發送接收流程。

一、電子郵件系統的基本網絡結構

如下圖:

public class ConfigHost { public string Server { get; set; } public int Port { get; set; } public string Username { get; set; } public string Password { get; set; } public bool EnableSsl { get; set; } } public class ConfigMail { public string From { get; set; } public string[] To { get; set; } public string Subject { get; set; } public string Body { get; set; } public string[] Attachments { get; set; } public string[] Resources { get; set; } }

同時定義一個統一的接口ISendMail,以方便測試和比較。

    public interface ISendMail
    {
        void CreateHost(ConfigHost host);
        void CreateMail(ConfigMail mail);
        void CreateMultiMail(ConfigMail mail);
        void SendMail();
    }

 

1、使用System.Net.Mail

System.Net.Mail屬於.Net Framework 的一部分,.Net2.0以後可以使用這個組件。

    using System.Net.Mail;
    public class UseNetMail : ISendMail
    {
        private MailMessage Mail { get; set; }
        private SmtpClient Host { get; set; }

        public void CreateHost(ConfigHost host)
        {
            Host = new SmtpClient(host.Server, host.Port);
            Host.Credentials = new System.Net.NetworkCredential(host.Username, host.Password);
            Host.EnableSsl = host.EnableSsl;
        }

        public void CreateMail(ConfigMail mail)
        {
            Mail = new MailMessage();
            Mail.From = new MailAddress(mail.From);

            foreach (var t in mail.To)
                Mail.To.Add(t);

            Mail.Subject = mail.Subject;
            Mail.Body = mail.Body;
            Mail.IsBodyHtml = true;
            Mail.BodyEncoding = System.Text.Encoding.UTF8;
        }

        public void CreateMultiMail(ConfigMail mail)
        {
            CreateMail(mail);

            Mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString("If you see this message, it means that your mail client does not support html.", Encoding.UTF8, "text/plain"));

            var html = AlternateView.CreateAlternateViewFromString(mail.Body, Encoding.UTF8, "text/html");
            foreach (string resource in mail.Resources)
            {
                var image = new LinkedResource(resource, "image/jpeg");
                image.ContentId = Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource)));
                html.LinkedResources.Add(image);
            }
            Mail.AlternateViews.Add(html);

            foreach (var attachment in mail.Attachments)
            {
                Mail.Attachments.Add(new Attachment(attachment));
            }
        }

        public void SendMail()
        {
            if (Host != null && Mail != null)
                Host.Send(Mail);
            else
                throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
        }
    }
2、使用OpenSmtp 開源的發送郵件組件,可以在這裡獲得源碼。但是OpenSmtp目前不支持SSL。
    using OpenSmtp.Mail;
    public class UseOpenSmtp : ISendMail
    {
        private MailMessage Mail { get; set; }
        private Smtp Host { get; set; }

        public void CreateHost(ConfigHost host)
        {
            Host = new Smtp(host.Server, host.Username, host.Password, host.Port);
        }

        public void CreateMail(ConfigMail mail)
        {
            Mail = new MailMessage();
            Mail.From = new EmailAddress(mail.From);
            foreach (var t in mail.To)
                Mail.AddRecipient(t, AddressType.To);
            
            Mail.HtmlBody = mail.Body;
            Mail.Subject = mail.Subject;
            Mail.Charset = "UTF-8";
        }

        public void CreateMultiMail(ConfigMail mail)
        {
            CreateMail(mail);
            foreach (var attachment in mail.Attachments)
            {
                Mail.AddAttachment(attachment);
            }
            foreach (var resource in mail.Resources)
            {
                Mail.AddImage(resource, Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource))));            
            }
        }

        public void SendMail()
        {
            if (Host != null && Mail != null)
                Host.SendMail(Mail);
            else
                throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
        }

3、使用LumiSoft.Net

LumiSoft.Net是非常強大的開源組件,不僅僅發送郵件,同樣也可用於接收郵件,是個人認為最好的開源組件了。在這裡可以詳細了解LumiSoft.Net組件的命名空間,也可以在這裡下載其源碼和樣例。

    using LumiSoft.Net.SMTP.Client;
    using LumiSoft.Net.AUTH;
    using LumiSoft.Net.Mail;
    using LumiSoft.Net.MIME;
    public class UseLumiSoft : ISendMail
    {
        private SMTP_Client Host { get; set; }
        private Mail_Message Mail { get; set; }

        public void CreateHost(ConfigHost host)
        {
            Host = new SMTP_Client();
            Host.Connect(host.Server, host.Port, host.EnableSsl);
            Host.EhloHelo(host.Server);
            Host.Auth(Host.AuthGetStrongestMethod(host.Username, host.Password));
        }

        public void CreateMail(ConfigMail mail)
        {
            Mail = new Mail_Message();
            Mail.Subject = mail.Subject;
            Mail.From = new Mail_t_MailboxList();
            Mail.From.Add(new Mail_t_Mailbox(mail.From, mail.From));
            Mail.To = new Mail_t_AddressList();
            foreach (var to in mail.To)
            {
                Mail.To.Add(new Mail_t_Mailbox(to, to));
            }
            var body = new MIME_b_Text(MIME_MediaTypes.Text.html);
            Mail.Body = body; //Need to be assigned first or will throw "Body must be bounded to some entity first" exception.
            body.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, mail.Body);
        }

        public void CreateMultiMail(ConfigMail mail)
        {
            CreateMail(mail);

            var contentTypeMixed = new MIME_h_ContentType(MIME_MediaTypes.Multipart.mixed);
            contentTypeMixed.Param_Boundary = Guid.NewGuid().ToString().Replace("-", "_");
            var multipartMixed = new MIME_b_MultipartMixed(contentTypeMixed);
            Mail.Body = multipartMixed;

            //Create a entity to hold multipart/alternative body
            var entityAlternative = new MIME_Entity();
            var contentTypeAlternative = new MIME_h_ContentType(MIME_MediaTypes.Multipart.alternative);
            contentTypeAlternative.Param_Boundary = Guid.NewGuid().ToString().Replace("-", "_");
            var multipartAlternative = new MIME_b_MultipartAlternative(contentTypeAlternative);
            entityAlternative.Body = multipartAlternative;
            multipartMixed.BodyParts.Add(entityAlternative);

            var entityTextPlain = new MIME_Entity();
            var plain = new MIME_b_Text(MIME_MediaTypes.Text.plain);
            entityTextPlain.Body = plain;
            plain.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, "If you see this message, it means that your mail client does not support html.");
            multipartAlternative.BodyParts.Add(entityTextPlain);

            var entityTextHtml = new MIME_Entity();
            var html = new MIME_b_Text(MIME_MediaTypes.Text.html);
            entityTextHtml.Body = html;
            html.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, mail.Body);
            multipartAlternative.BodyParts.Add(entityTextHtml);

            foreach (string attachment in mail.Attachments)
            {
                multipartMixed.BodyParts.Add(Mail_Message.CreateAttachment(attachment));
            }

            foreach (string resource in mail.Resources)
            {
                var entity = new MIME_Entity();
                entity.ContentDisposition = new MIME_h_ContentDisposition(MIME_DispositionTypes.Inline);
                entity.ContentID = Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource))); //eg.<img src="cid:ContentID"/>
                var image = new MIME_b_Image(MIME_MediaTypes.Image.jpeg);
                entity.Body = image;
                image.SetDataFromFile(resource, MIME_TransferEncodings.Base64);
                multipartMixed.BodyParts.Add(entity);
            }
        }

        public void SendMail()
        {
            if (Host != null && Mail != null)
            {
                foreach (Mail_t_Mailbox from in Mail.From.ToArray())
                {
                    Host.MailFrom(from.Address, -1);
                }
                foreach (Mail_t_Mailbox to in Mail.To)
                {
                    Host.RcptTo(to.Address);
                }
                using (var stream = new MemoryStream())
                {
                    Mail.ToStream(stream, new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q, Encoding.UTF8), Encoding.UTF8);
                    stream.Position = 0;//Need to be reset to 0, otherwise nothing will be sent;
                    Host.SendMessage(stream);
                    Host.Disconnect();
                }
            }
            else
                throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
        }
    }

閱讀LumiSoft.Net的源代碼,可以看到LumiSoft.Net編程嚴格遵循了RFC(Request For Comments)定義的協議規范。通過閱讀這些源碼對於了解RFC和其中關於郵件網絡協議規范也是非常有幫助的。如果想查閱RFC文檔可以通過這個鏈接。

在上面的代碼中MIME_MediaTypes類,MIME_TransferEncodings類和Encoding類(System.Text.Encoding)都是或類似於枚舉,設置了郵件內容的編碼方式或解析方式,這個幾個類從根本上決定了郵件的正常傳輸和顯示。MIME_TransferEncodings類設置了文件傳輸編碼,決定郵件頭中的Content-Transfer-Encoding字段的值及其他需要傳輸編碼字段的編碼方式(如標題中的多國語言)。MIME_MediaTypes類設置郵件各部分內容的類型,決定郵件中Content-Type字段的值。而Encoding類不用說,決定了charset的值。關於這些設置的具體作用下文還將提到,這裡略過。

4、測試

下表是通過網絡搜集的各大SMTP服務器的配置情況,可以選擇使用這些配置進行測試:

服務商 SMTP地址 SMTP端口 EnableSsl gmail smtp.google.com 25, 465 or 587 true 126 smtp.126.com 25 false 163 smtp.126.com 25 false hotmail smtp.live.com 25 true sina smtp.sina.com 25 false sohu smtp.sohu.com 25 false

新建控制台應用程序,測試發送只包含正文的簡單郵件:

    class Program
    {
        static void Main(string[] args)
        {           
            var h1 = new ConfigHost()
            {
                Server = "smtp.gmail.com",
                Port = 465,
                Username = "******@gmail.com",
                Password = "******",
                EnableSsl = true
            };
            var m1 = new ConfigMail()
            {
                Subject = "Test",
                Body = "Just a test.",
                From = "******@gmail.com",
                To = new string[] { "******@gmail.com" },  
            };

            var agents = new List<ISendMail>() { new UseNetMail(), new UseOpenSmtp(), new UseLumiSoft() };
            foreach (var agent in agents)
            {
                var output = "Send m1 via h1 " + agent.GetType().Name + " ";
                Console.WriteLine(output + "start");
                try
                {
                    agent.CreateHost(h1);
                    m1.Subject = output;        
                    agent.CreateMail(m1);
                    agent.SendMail();
                    Console.WriteLine(output + "success");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                Console.WriteLine(output + "end");
                Console.WriteLine("-----------------------------------");
            }
            Console.Read();
        }
    }

通過gmail發送郵件時,OpenSmtp由於不支持SSL發送失敗,NetMail使用587端口能夠成功發送,LumiSoft使用465端口能夠成功發送。查閱Gmail相關文檔,描述說Gmail的465端口使用SSL協議,而587端口使用TLS協議,但587是需要STARTTLS命令支持才能提升為TLS。在命令提示符下測試發現的確需要在發送STARTTLS命令後才能使用TLS協議:

> telnet smtp.gmail.com 587
220 mx.google.com ESMTP o5sm40420786eeg.8 - gsmtp
EHLO g1
250-mx.google.com at your service, [173.231.8.212]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250 CHUNKING
AUTH LOGIN
530 5.7.0 Must issue a STARTTLS command first. o5sm40420786eeg.8 – gsmtpSTARTTLS220 
STARTTLS
2.0.0 Ready to start TLS
QUIT

對於TLS與STARTTLS人們經常搞混,這裡找到一篇關於它們的解釋,請點擊這裡。

因而LumiSoft如果連接gmail服務器時還需明確發送STARTTLS命令,已經發現LumiSoft有相關方法SMTP_Client.StartTLS(),連接gmail相較其他smtp服務器還是較為復雜些。另外一些服務器要求郵件配置中的Username必須與From相一致,需要特別注意。

 

測試發送帶附件和內嵌資源的郵件:

    class Program
    {
        static void Main(string[] args)
        {
            var h2 = new ConfigHost()
            {
                Server = "smtp.163.com",
                Port = 25,
                Username = "******@163.com",
                Password = "******",
                EnableSsl = false
            };
            var m2 = new ConfigMail()
            {
                Subject = "Test",
                Body = "Just a test. <br/><img src='cid:" + Convert.ToBase64String(Encoding.Default.GetBytes("Resource.jpg")) + "' alt=''/> ", 
                From = "******@163.com",
                To = new string[] { "******@163.com" }, 
                Attachments = new string[] { @"E:\Test\SendMail\Attachment.pdf" }, 
                Resources = new string[] { @"E:\Test\SendMail\Resource.jpg" } 
            };
          
            var agents = new List<ISendMail>() { new UseNetMail(), new UseOpenSmtp(), new UseLumiSoft() };
            foreach (var agent in agents)
            {
                var output = "Send m2 via h2 " + agent.GetType().Name + " ";
                Console.WriteLine(output + "start");
                try
                {
                    agent.CreateHost(h2);
                    m2.Subject = output;        
                    agent.CreateMultiMail(m2);
                    agent.SendMail();
                    Console.WriteLine(output + "success");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                Console.WriteLine(output + "end");
                Console.WriteLine("-----------------------------------");
            }
            Console.Read();
        }
    }

 

C#郵件發送問題(二)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved