見出し画像

メール送信機能の開発(C#篇)

前書き

.Net Framework 4.5からは、MailKitというライブラリでメール送信機能を実現することが推奨されているので、今回はMailKitを使ってメール送信機能を実現してみます。

本文の対象者は下記の通りです:
■C#言語の知識のある方
■Visual Studioを使ったことのある方
■デスクトップ(windows)アプリケーション開発に関する知識のある方

プログラムの構造図

ソースコードの移植性を考慮し(将来、外のアプリでも使うかどうか分かりませんが)、メール送信機能をまとめて1つのクラス(CEmailSender)に実装します。メール送信が必要な場合、CEmailSenderクラスを作成し、SendMail()というメソッドを呼び出すだけで、メールが送信されるようにします。
クラス図は下記の通りです。

クラス図-EmailSender

CEmailSenderクラスを使ってメールを送信するシーケンス図は下記の通りです。

シーケンス図-EmailSender

上図のように、メール送信機能の全てをCEmailSenderというクラスに実装しておくことで、将来あらゆるアプリ(呼び出し側)に簡単にメール送信機能を搭載することが可能になります。

プログラミング(開発)

■必要資材

Visual Studio 2019 Community - C#プログラミング統合環境(IDE)
 ※Visual Studioについては、こちらをご覧ください。
Microsoft .Net Framework 4.5
 ※無い場合は、Microsoft公式ホームページでダウンロード可能

画像3

プロジェクトの作成

メール送信機能を「部品」として作成したいので、下記のように「クラスライブラリー(DLL)」の形で実装します。

画像4

「次へ」を押して、プロジェクト名、保存ディレクトリと.NET Frameworkのバージョン(.NET Framework 4.5以降)を指定し、「作成」を押します。

画像5

こうすると、C#のプロジェクトが作成されますが、デフォルトではまだMailKitが使えません。
※MailKitは下記のようにインストールする必要があります。

□MailKitのインストール方法

下図のように、「ツール」>「NuGet Package manager」> 「Manage Nuget Packages for Solution...」を押します。

画像6

下図のように、MailKitを検索し、インストールします。

画像7

インストールが完了すると、下図のように、自動的にMailKitライブラリーが参照されるようになります。

画像8

■各クラスの実装

□CMailAddrAndNameクラス

メールアドレスと名前をペアで管理するために、両方を格納するクラスを作成します。

namespace jdr_core.EmailSender
{
   /// <summary>
   /// メールアドレスと名前を格納するクラス
   /// </summary>
   public class CMailAddrAndName
   {
       public string MailAddress { get; }
       public string Name { get; }

       public CMailAddrAndName(string mailAddress, string name)
       {
           this.MailAddress = mailAddress;
           this.Name = name;
       }

   }//end class
}//end namespace

・ネームスペースは、jdr_core.EmailSenderを指定していますが、任意です。
・メールアドレスと名前を指定してインスタンスを作成するよう、引数付のコンストラクターを作成しています。

□CEmailContentTypeクラス

EMailの形式には、テキスト型とHTML型があります。今回はこの両方の形式を全部サポートするので、メール本文の形式を識別するための列挙型を作成します。

namespace jdr_core.EmailSender
{
   /// <summary>
   /// メール本文の型
   /// </summary>
   public enum CEmailContentType
   {
       TEXT,  //テキスト型
       HTML   //HTML型
   }//end enum
}//end namespace

□CEmailSenderクラス

・コード抜粋1

       private string SMTPHost = "";
       private int SMTPPort = 25;    //平文:25  SSL:465
       private bool isSSLConnection = false;
       private string SMTPAccount = "";
       private string SMTPPassword = "";

       //SMTPサーアがユーザ認証を必要とするフラグ
       private bool mustAuthenticate = false;

メール送信サーバに関する設定値です。
SMTPHost:メール送信サーバのホスト名です。
SMTPPort:使うポート番号です。
isSSLConnection:メール送信にSSLを使うかどうかを指定する変数です。(今現在、SSLを使わないメールサーバはないでしょう。)
SMTPAccount / SMTPPassword:メールサーバに接続するためのアカウントとパスワードです。
mustAuthenticate:ユーザ認証を必要とするかどうかの変数です。

※上記の設定値はメールサーバによって違ってくるので、自分のメールサーバの設定値を参照してください。

・コード抜粋2

       /// <summary>
       /// コンストラクター
       /// </summary>
       /// <param name="smtpHost">SMTPホスト</param>
       /// <param name="smtpPort">SMTPポート</param>
       /// <param name="account">アカウント</param>
       /// <param name="password">パスワード</param>
       /// <param name="isSSLConnect">SSL接続</param>
       public CEmailSender(string smtpHost, int smtpPort, string account, string password, bool isSSLConnect = true, bool mustAuthenticate = true)
       {
           this.SMTPHost = smtpHost;
           this.SMTPPort = smtpPort;
           this.isSSLConnection = isSSLConnect;
           this.SMTPAccount = account;
           this.SMTPPassword = password;
           this.mustAuthenticate = mustAuthenticate;
       }       

メールサーバの各設定値を指定してCEmailSenderのインスタンスを作成するように、引数付きのコンストラクターを作成しています。

・コード抜粋3

       /// <summary>
       /// メール送信
       /// </summary>
       /// <param name="toEmailAddrLst">宛先メールアドレス群</param>
       /// <param name="ccEmailAddrLst">CCメールアドレス群</param>
       /// <param name="bccEmailAddrLst">BCCメールアドレス群</param>
       /// <param name="fromEmailAddr">送信者メールアドレス</param>
       /// <param name="subject">題名</param>
       /// <param name="content">メール本文</param>
       /// <param name="attachFileLst">添付ファイルリスト</param>
       /// <param name="contentType">メール本文の形式(TEXT or HTML)</param>
       public /*async*/ void SendMail(List<CMailAddrAndName> toEmailAddrLst,
                                      List<CMailAddrAndName> ccEmailAddrLst,
                                      List<CMailAddrAndName> bccEmailAddrLst, 
                                      CMailAddrAndName fromEmailAddr,
                                      string subject,
                                      string content,
                                      List<string> attachFileLst,
                                      CEmailContentType contentType = CEmailContentType.TEXT)
       {
          ...
        }

メールを送信するメソッドです。
宛先メールアドレス、CCするメールアドレス、BCCするメールアドレスはそれぞれ複数を指定できるように、List<CMailAddrAndName>で定義しています。(複数の宛先に一括送信可能)

・コード抜粋4

TextPart textPart = null;
switch (contentType)
{
   case CEmailContentType.TEXT:  //テキスト型
      textPart = new TextPart(MimeKit.Text.TextFormat.Plain);
      break;
   case CEmailContentType.HTML: //HTML型
      textPart = new TextPart(MimeKit.Text.TextFormat.Html);
      break;
}//end switch

テキスト型とHTML型のメール本文を構築することができます。

・コード抜粋5

if (attachFileLst == null || attachFileLst.Count == 0)  //添付ファイル無し
{
   msg.Body = textPart;  //メール本文設定
}
else  //添付ファイル有
{
   Multipart multipart = new Multipart("Multipart/Mixed");
   multipart.Add(textPart);  //メール本文設定

   foreach(string curFile in attachFileLst)
   {
       //添付ファイルはバイナリデータとして扱う
       MimePart attachement = new MimePart("Application/Octet-Stream")
       {
           Content = new MimeContent(File.OpenRead(curFile)),
           ContentDisposition = new ContentDisposition(),
           ContentTransferEncoding = ContentEncoding.Base64,
           FileName = Path.GetFileName(curFile)
       };  //end new MimePart()

       multipart.Add(attachement);
   }//end foreach

   msg.Body = multipart;
}//end if

今回は添付ファイル送信機能もサポートすることにしました。
メールに添付したいファイルパスをattachFileLstに格納して置くと(複数指定可能)、メールを一緒に送信してくれます。
※メールサーバによって、添付可能なファイルの容量がそれぞれ違います。

・コード抜粋6

using (SmtpClient client = new SmtpClient())
{
   WriteDbgLog(curMethodName, "SMTPサーバへ接続中...");

   client.Connect(this.SMTPHost, this.SMTPPort, true);  //use SSL: true ファイル添付時必須

   WriteDbgLog(curMethodName, "SMTPサーバへ接続完了。");

   //SMTPサーバがユーザ認証を必要とする場合
   if (this.mustAuthenticate)
   {
       //SMTPサーバユーザ認証
       client.Authenticate(this.SMTPAccount, this.SMTPPassword);
       WriteDbgLog(curMethodName, "SMTPサーバ認証完了。");
   }//end if

   WriteDbgLog(curMethodName, "メール送信中...");

   client.Send(msg);
   WriteDbgLog(curMethodName, "メール送信済み。");

   client.Disconnect(true);
   WriteDbgLog(curMethodName, "STMPサーバとの接続切断。");
}//end using

SmtpClientのインスタンスを生成し、Send()メソッドを呼び出し、メールを送信しています。

・CEmailSenderクラスのコード


using MailKit.Net.Smtp;
using MimeKit;
using System;
using System.Collections.Generic;
using System.IO;

namespace jdr_core.EmailSender
{
   /// <summary>
   /// email送信機能
   /// 
   /// FW4.5からは MailKitライブラリを推奨。
   /// MailKitはとても使いやすい。FW4.5以降では、MailKitを使うべき。
   /// 提供API:
   /// ・SMTPクライアント    メール送信
   /// ・POP3クライアント    メール受信
   /// ・IMAP4クライアント   メール管理
   /// 
   /// 例: googleのメールサーバ
   /// 
   /// string smtpHost = "smtp.gmail.com";
   /// int smtpPort = 465;  //←SSL  平文→ 25;
   /// bool isSSLConn = true;
   /// string smtpAcc = "huilingtech@gmail.com";
   /// string pwd = "XXXXXXX";
   /// 
   /// CEmailSender mailSender = new CEmailSender(smtpHost, smtpPort, smtpAcc, pwd, isSSLConn);
   /// 
   /// mailSender.SendMail(toAddrLst, ccAddrLst, bccAddrLst, fromAddr, subject, content, fileLst, CEmailContentType.HTML);
   /// </summary>
   public class CEmailSender
   {
       private const string CLASS_FULL_NAME = "CEmailSender";

       private string SMTPHost = "";
       private int SMTPPort = 25;    //平文:25  SSL:465
       private bool isSSLConnection = false;
       private string SMTPAccount = "";
       private string SMTPPassword = "";

       //SMTPサーアがユーザ認証を必要とするフラグ
       private bool mustAuthenticate = false;

       /// <summary>
       /// DEBUGログを出力する
       /// </summary>
       /// <param name="curMethodName">メソッド名</param>
       /// <param name="msg">ログメッセージ</param>
       private void WriteDbgLog(string curMethodName, string msg)
       {
           CLogger.Log(CLogLevel.DEBUG, CLASS_FULL_NAME, curMethodName, msg, null);
       }

       /// <summary>
       /// ERRORログを出力する
       /// </summary>
       /// <param name="curMethodName"></param>
       /// <param name="msg"></param>
       private void WriteErrorLog(string curMethodName, string msg)
       {
           CLogger.Log(CLogLevel.ERROR, CLASS_FULL_NAME, curMethodName, msg, null);
       }

       /// <summary>
       /// ERRORログを出力する
       /// </summary>
       /// <param name="curMethodName"></param>
       /// <param name="msg"></param>
       /// <param name="cause"></param>
       private void WriteErrorLog(string curMethodName, string msg, Exception cause)
       {
           CLogger.Log(CLogLevel.ERROR, CLASS_FULL_NAME, curMethodName, msg, cause);
       }

       /// <summary>
       /// コンストラクター
       /// </summary>
       /// <param name="smtpHost">SMTPホスト</param>
       /// <param name="smtpPort">SMTPポート</param>
       /// <param name="account">アカウント</param>
       /// <param name="password">パスワード</param>
       /// <param name="isSSLConnect">SSL接続</param>
       public CEmailSender(string smtpHost, int smtpPort, string account, string password, bool isSSLConnect = true, bool mustAuthenticate = true)
       {
           this.SMTPHost = smtpHost;
           this.SMTPPort = smtpPort;
           this.isSSLConnection = isSSLConnect;
           this.SMTPAccount = account;
           this.SMTPPassword = password;
           this.mustAuthenticate = mustAuthenticate;
       }

       /// <summary>
       /// メール送信
       /// </summary>
       /// <param name="toEmailAddrLst">宛先メールアドレス群</param>
       /// <param name="ccEmailAddrLst">CCメールアドレス群</param>
       /// <param name="bccEmailAddrLst">BCCメールアドレス群</param>
       /// <param name="fromEmailAddr">送信者メールアドレス</param>
       /// <param name="subject">題名</param>
       /// <param name="content">メール本文</param>
       /// <param name="attachFileLst">添付ファイルリスト</param>
       /// <param name="contentType">メール本文の形式(TEXT or HTML)</param>
       public /*async*/ void SendMail(List<CMailAddrAndName> toEmailAddrLst,
                                      List<CMailAddrAndName> ccEmailAddrLst,
                                      List<CMailAddrAndName> bccEmailAddrLst, 
                                      CMailAddrAndName fromEmailAddr,
                                      string subject,
                                      string content,
                                      List<string> attachFileLst,
                                      CEmailContentType contentType = CEmailContentType.TEXT)
       {
           string curMethodName = "SendMail()";

           //宛先メールアドレス無し
           if (toEmailAddrLst.Count == 0)
           {
               WriteErrorLog(curMethodName, "宛先メールアドレスが指定されていません。");
               return;
           }//end if

           try
           {
               MimeMessage msg = new MimeMessage();

               //宛先メールアドレス設定
               foreach (CMailAddrAndName tmp in toEmailAddrLst)
               {
                   msg.To.Add(new MailboxAddress(tmp.Name, tmp.MailAddress));
               }//end foreach

               //CCメールアドレス設定
               foreach (CMailAddrAndName tmp in ccEmailAddrLst)
               {
                   msg.Cc.Add(new MailboxAddress(tmp.Name, tmp.MailAddress));
               }//end foreach

               //BCCメールアドレス設定
               foreach(CMailAddrAndName tmp in bccEmailAddrLst)
               {
                   msg.Bcc.Add(new MailboxAddress(tmp.Name, tmp.MailAddress));
               }//end foreach

               //fromメールアドレス設定
               msg.From.Add(new MailboxAddress(fromEmailAddr.Name, fromEmailAddr.MailAddress));

               msg.Subject = subject;

               TextPart textPart = null;
               switch (contentType)
               {
                   case CEmailContentType.TEXT:  //テキスト型
                       textPart = new TextPart(MimeKit.Text.TextFormat.Plain);
                       break;
                   case CEmailContentType.HTML: //HTML型
                       textPart = new TextPart(MimeKit.Text.TextFormat.Html);
                       break;
               }//end switch

               //メール本文
               textPart.Text = content;

               if (attachFileLst == null || attachFileLst.Count == 0)  //添付ファイル無し
               {
                   msg.Body = textPart;  //メール本文設定
               }
               else  //添付ファイル有
               {
                   Multipart multipart = new Multipart("Multipart/Mixed");
                   multipart.Add(textPart);  //メール本文設定

                   foreach(string curFile in attachFileLst)
                   {
                       //添付ファイルはバイナリデータとして扱う
                       MimePart attachement = new MimePart("Application/Octet-Stream")
                       {
                           Content = new MimeContent(File.OpenRead(curFile)),
                           ContentDisposition = new ContentDisposition(),
                           ContentTransferEncoding = ContentEncoding.Base64,
                           FileName = Path.GetFileName(curFile)
                       };  //end new MimePart()

                       multipart.Add(attachement);
                   }//end foreach

                   msg.Body = multipart;
               }//end if
               
               using (SmtpClient client = new SmtpClient())
               {
                   WriteDbgLog(curMethodName, "SMTPサーバへ接続中...");

                   client.Connect(this.SMTPHost, this.SMTPPort, true);  //use SSL: true ファイル添付時必須

                   WriteDbgLog(curMethodName, "SMTPサーバへ接続完了。");

                   //SMTPサーバがユーザ認証を必要とする場合
                   if (this.mustAuthenticate)
                   {
                       //SMTPサーバユーザ認証
                       client.Authenticate(this.SMTPAccount, this.SMTPPassword);
                       WriteDbgLog(curMethodName, "SMTPサーバ認証完了。");
                   }//end if

                   WriteDbgLog(curMethodName, "メール送信中...");

                   client.Send(msg);
                   WriteDbgLog(curMethodName, "メール送信済み。");

                   client.Disconnect(true);
                   WriteDbgLog(curMethodName, "STMPサーバとの接続切断。");
               }//end using

           }
           catch(Exception e)
           {
               WriteErrorLog(curMethodName, "例外発生", e);
               throw;
           }//end try
       }

   } // end class
}  // end namespace

■ビルド

上記のプロジェクトをビルドすると、下図のように、jdr-core-csharp.dllというDLLファイルが作成されます。
※DLLファイル名は指定したプロジェクト名になります。

画像9

このDLLファイルを他のプロジェクトで参照することによって、CEmailSenderクラスを使ってメールを送信することが可能になります。

上記のDLLを使ったテストメール

画像10

※HTML形式のメールを送信しています(h3, div, span, aタグ使用)。
※画像ファイルを4つ添付しています(因みに、画像内容は大連のオフョア開発会社の様子です。)。

■おまけ

googleのメールサーバを使う場合、セキュリティ検査が強化されたため、このままではメール送信処理が失敗してしまいます。

下図のように、googleサイトにログインし、googleアカウントの「安全性の低いアプリのアクセス」の設定をオンにすることでメール送信が可能になります。

画像11

まとめ

今回は.NET Frameworkを使って、C#プログラミング言語でメール送信機能を実装する方法について解説しました。

そして、アプリを開発する前に、先ずプログラムの設計(クラス図、シーケンス図)をしてから手お動かすことをお勧めします。クラス図は実際に作成しなくても、少なくとも頭の中で「どういうクラスを作るのか?どんな順番でどのメソッドを呼び出すのか?」のようなイメージができた後じゃないと、色々バグの原因になります。

では、バイバイ! Have a nice day!

この記事が気に入ったらサポートをしてみませんか?