iT邦幫忙

2021 iThome 鐵人賽

DAY 3
2
Modern Web

ASP.NET Web Forms 入門 - 30天建立遊艇網頁專案後端及後台功能 C#系列 第 3

Day 3 - Using the Gmail SMTP Server to Send Emails with ASP.NET Web Forms C# 使用 Gmail 做為郵件伺服器來寄信

=x= 🌵 CONTACT Page 寄信頁的後端寄信功能及其它注意事項。


Gmail SMTP Server 介紹 :

📌 如同現實世界,如果要寄出一封信,你會需要郵局的服務,而當你要寄出 email 你就需要一個郵件伺服器來替你服務,會選擇 Google 提供的服務,是因為它簡單、安全且免費,而 SMTP (Simple Mail Transfer Protocol) 可以把它當成傳送 email 的一種共通規定,實際使用上只需要宣告你要用這種方式進行傳送即可,網路上已有相當多介紹寄信作法的文章,本文將簡單介紹實作時的過程,並在文末總結後端製作 CONTACT Page 時的其它注意事項。



CONTACT Page - Gmail SMTP 寄信實作 :

1. 建立一個專用的 Google 帳號 (可選擇業務管理用)。


2. 啟用「兩步驟驗證」並設定「應用程式密碼」

https://ithelp.ithome.com.tw/upload/images/20210917/20139487rBQ2GxQr3O.jpg

  • 👀 設定方式可參考 : 如何使用Google SMTP寄信(兩段式驗證+應用程式密碼)

  • 🌵 產生的16碼應用程式密碼會在稍後用到。

  • 🌵 如果帳戶有忘記密碼重新設定,16碼應用程式密碼會被清掉,需要再生成一次。

  • 👺 原本在建立功能時並沒有啟用兩步驟驗證及設定應用程式密碼,只有依網路的教學在帳號的安全性設定裡,設定啟用"允許安全性較低的應用程式" (開啟兩步驟驗證後即失效),一開始都可以正常使用寄信功能,但不久後就出現錯誤,爬文並測試發現如果關閉防毒時寄信功能就正常,依網路建議設定兩步驟驗證及應用程式密碼後就可正常使用,而且無需開啟低安全性也不用關閉防毒

https://ithelp.ithome.com.tw/upload/images/20210917/20139487B8vgQDvl2x.jpg

  • 🦠 (未設定應用程式密碼遇到的錯誤)

3. 建立同名 .aspx 頁面,複製貼上 .html裡 <head><body> 中的內容。


4. 因為 Web Form 只能有一個 <form> 標籤,保留新建時的 <form> 標籤,刪除來自 .html 內的 <form> (複製原設定到保留的標籤內)

<form runat="server" method="post" name="aspnetForm" id="aspnetForm">
  • 🌵 原本標籤裡的action="contact.aspx"需要拿掉,否則只會直接跳轉刷新頁面,無法進入送出信件的按鍵事件。

5. 刪除用不到下列程式碼 (Web Forms 自動產生的)

<div>
  <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTM4NzgwOTUzNw8WAh4HQ2FwdGNoYQUEMzc5ORYCZg9kFgICAw9kFgICAQ9kFgYCBw8QDxYCHgtfIURhdGFCb3VuZGdkEBUIA1VTQQRBU0lBBkVVUk9QRQ1OT1JUSCBBTUVSSUNBD0NFTlRSQUwgQU1FUklDQQ1TT1VUSCBBTUVSSUNBBkFGUklDQQdPQ0VBTklBFQgDVVNBBEFTSUEGRVVST1BFDU5PUlRIIEFNRVJJQ0EPQ0VOVFJBTCBBTUVSSUNBDVNPVVRIIEFNRVJJQ0EGQUZSSUNBB09DRUFOSUEUKwMIZ2dnZ2dnZ2dkZAIJDxAPFgIfAWdkEBUHCVRheWFuYSAzNwlUYXlhbmEgNDYJVGF5YW5hIDQ4GlRheWFuYSA1NCAgIChOZXcgQnVpbGRpbmcpCVRheWFuYSA1OAlUYXlhbmEgNjQISVNBUkEgNTAVBwlUYXlhbmEgMzcJVGF5YW5hIDQ2CVRheWFuYSA0OBpUYXlhbmEgNTQgICAoTmV3IEJ1aWxkaW5nKQlUYXlhbmEgNTgJVGF5YW5hIDY0CElTQVJBIDUwFCsDB2dnZ2dnZ2dkZAINDxYCHgNzcmMFDkpwZWdJbWFnZS5hc2h4ZBgBBR5fX0NvbnRyb2xzUmVxdWlyZVBvc3RCYWNrS2V5X18WAQUmY3RsMDAkQ29udGVudFBsYWNlSG9sZGVyMSRJbWFnZUJ1dHRvbjG5FQQyUaUwxwcePcF6X9308WUP8w==" />
</div>
<div>
  <input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="CD2448B2" />
  <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWFgKR7pRKAt6w6N0JArepvugGAuXBvr8EAo2X6PQHAvHN768EAsylpNwNArDts50HAu3a2roPApiq1MAIArWniMcJApDl3ucMAs7G2I0NAs/GtMoIAtnGtMoIAszJo6EGAtnGwO4BAs3GnJwHAvuDy+cMAu3CxJMMAp/KzpkCAr28ifwM0JmHZRHwnNWAdZdFwWYody60nD0=" />
</div>


6. 使用搜尋功能將 .html 全部取代為 .aspx。


7. CONTACT Page 寄信頁修改

  • a. 的輸入框改成 asp:TextBox 控制項,並限制字數輸入長度及格式,加入空白提示。
  • b. 下拉選單改成 asp:DropDownList 控制項。
  • c. submit 按鈕改成 asp:ImageButton 控制項。
<!--表單-->
<div class="from01">
    <p>
        Please Enter your contact information<span class="span01">*Required</span>
    </p>
    <br />
    <table>
        <tr>
            <td class="from01td01">Name :</td>
            <td><span>*</span><asp:TextBox runat="server" name="Name" type="text" ID="Name" class="{validate:{required:true, messages:{required:'Required'}}}" Style="width: 250px;" required="" aria-required="true" oninput="setCustomValidity('');" oninvalid="setCustomValidity('Required!')" MaxLength="50"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <td class="from01td01">Email :</td>
            <td><span>*</span><asp:TextBox runat="server" name="Email" type="text" ID="Email" class="{validate:{required:true, email:true, messages:{required:'Required', email:'Please check the E-mail format is correct'}}}" Style="width: 250px;" required="" aria-required="true" oninput="setCustomValidity('');" oninvalid="setCustomValidity('Required!')" MaxLength="50"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <td class="from01td01">Phone :</td>
            <td><span>*</span><asp:TextBox runat="server" name="Phone" type="text" ID="Phone" class="{validate:{required:true, messages:{required:'Required'}}}" Style="width: 250px;" required="" aria-required="true" oninput="setCustomValidity('');" oninvalid="setCustomValidity('Required!')" MaxLength="50"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <td class="from01td01">Country :</td>
            <td><span>*</span>
                <asp:DropDownList name="Country" id="Country" runat="server" DataTextField="countrySort" DataValueField="countrySort" DataSourceID="SqlDataSource1"></asp:DropDownList>
                <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:TayanaYachtConnectionString %>" SelectCommand="SELECT [countrySort] FROM [CountrySort]"></asp:SqlDataSource>
            </td>
        </tr>
        <tr>
            <td colspan="2"><span>*</span>Brochure of interest *Which Brochure would you like to view?</td>
        </tr>
        <tr>
            <td class="from01td01"> </td>
            <td>
                <asp:DropDownList name="Yachts" id="Yachts" runat="server" DataTextField="type" DataValueField="type"></asp:DropDownList>
            </td>
        </tr>
        <tr>
            <td class="from01td01">Comments:</td>
            <td>
                <asp:TextBox runat="server" TextMode="MultiLine" name="Comments" Rows="2" cols="20" ID="Comments" Style="height: 150px; width: 330px;" MaxLength="500"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <td class="from01td01"> </td>
            <td class="f_right">
                <!-- Render recaptcha API script -->
                <cc1:RecaptchaApiScript ID="RecaptchaApiScript1" runat="server" />
                <!-- Render recaptcha widget -->
                <cc1:RecaptchaWidget ID="Recaptcha1" runat="server" />
                <asp:Label ID="lblMessage" runat="server" Visible="False" ForeColor="Red"></asp:Label>
            </td>
        </tr>
        <tr>
            <td class="from01td01"> </td>
            <td class="f_right">
                <asp:ImageButton runat="server" type="image" name="ImageButton1" id="ImageButton1" src="images/buttom03.gif" style="border-width: 0px;" Height="25px" OnClick="ImageButton1_Click"/>
            </td>
        </tr>
    </table>
</div>
<!--表單-->
  • 🌵 Country 的下拉選單無特殊處理,直接在設計頁面點選精靈連結資料庫。

8. 在後置程式碼 Page_Load 事件加入以下程式碼取得型號下拉選單選項

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack) {
        loadModelList();
    }
}


9. 建立取得下拉選單遊艇型號 loadModelList(); 方法程式邏輯

private void loadModelList()
{
    //1.連線資料庫
    SqlConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["TayanaYachtConnectionString"].ConnectionString);
    //2.sql語法
    string sql = "SELECT * FROM Yachts";
    //3.創建command物件
    SqlCommand command = new SqlCommand(sql, connection);
    //取得遊艇型號分類
    connection.Open();
    SqlDataReader readerType = command.ExecuteReader();
    while (readerType.Read()) {
        string typeStr = readerType["yachtModel"].ToString();
        string isNewDesign = readerType["isNewDesign"].ToString();
        string isNewBuilding = readerType["isNewBuilding"].ToString();
        //加入遊艇型號下拉選單選項
        ListItem listItem = new ListItem();
        if (isNewDesign.Equals("True")) {
            listItem.Text = $"{typeStr} (New Design)";
            listItem.Value = $"{typeStr} (New Design)";
            Yachts.Items.Add(listItem);
        }
        else if (isNewBuilding.Equals("True")) {
            listItem.Text = $"{typeStr} (New Building)";
            listItem.Value = $"{typeStr} (New Building)";
            Yachts.Items.Add(listItem);
        }
        else {
            listItem.Text = typeStr;
            listItem.Value = typeStr;
            Yachts.Items.Add(listItem);
        }
    }
    connection.Close();
}

10. 內嵌地圖可使用 Google 的 My maps 設定好後複製網址更新到 <iframe> 的 src 位置。

11. 參考 Day2 文章實作機器人驗證並在寄信送出按鈕事件中加入以下程式碼

protected void ImageButton1_Click(object sender, ImageClickEventArgs e)
{
    if (String.IsNullOrEmpty(Recaptcha1.Response)) {
        lblMessage.Visible = true;
        lblMessage.Text = "Captcha cannot be empty.";
    }
    else {
        var result = Recaptcha1.Verify();
        if (result.Success) {
                        //驗證成功則寄出信件並送出警告提醒
            sendGmail();
            Response.Write("<script>alert('Thank you for contacting us!');location.href='contact.aspx';</script>");
        }
        else {
            lblMessage.Text = "Error(s): ";

            foreach (var err in result.ErrorCodes) {
                lblMessage.Text = lblMessage.Text + err;
            }
        }
    }
}


12. 使用 Nuget 套件管理安裝 MailKit (微軟官網建議使用)

https://ithelp.ithome.com.tw/upload/images/20210917/20139487HWxedFFvXi.jpg

13. 建立 sendGmail(); 方法程式碼,郵件內容可用 Html 標籤撰寫

using MailKit.Net.Smtp;
using MimeKit;

public void sendGmail()
{
    //宣告使用 MimeMessage
    var message = new MimeMessage();
    //設定發信地址 ("發信人", "發信 email")
    message.From.Add(new MailboxAddress("TayanaYacht", "XXXXXXX@gmail.com"));
    //設定收信地址 ("收信人", "收信 email")
    message.To.Add(new MailboxAddress(Name.Text.Trim(), Email.Text.Trim()));
    //寄件副本email
    message.Cc.Add(new MailboxAddress("收信人名稱", "XXXXXXX@gmail.com"));
    //設定優先權
    //message.Priority = MessagePriority.Normal;
    //信件標題
    message.Subject = "TayanaYacht Auto Email";
    //建立 html 郵件格式
    BodyBuilder bodyBuilder = new BodyBuilder();
    bodyBuilder.HtmlBody = 
        "<h1>Thank you for contacting us!</h1>" +
        $"<h3>Name : {Name.Text.Trim()}</h3>" +
        $"<h3>Email : {Email.Text.Trim()}</h3>" +
        $"<h3>Phone : {Phone.Text.Trim()}</h3>" +
        $"<h3>Country : {Country.SelectedValue}</h3>" +
        $"<h3>Type : {Yachts.SelectedValue}</h3>" +
        $"<h3>Comments : </h3>" +
        $"<p>{Comments.Text.Trim()}</p>";
    //設定郵件內容
    message.Body = bodyBuilder.ToMessageBody(); //轉成郵件內容格式

    using (var client = new SmtpClient()) {
        //有開防毒時需設定 false 關閉檢查
        client.CheckCertificateRevocation = false;
        //設定連線 gmail ("smtp Server", Port, SSL加密) 
        client.Connect("smtp.gmail.com", 587, false); // localhost 測試使用加密需先關閉 

        // Note: only needed if the SMTP server requires authentication
        client.Authenticate("XXXXXXX@gmail.com", "16碼應用程式密碼");
                //發信
        client.Send(message);
                //結束連線
        client.Disconnect(true);
    }
}
  • 👺 如有用using System.Net.Mail記得刪除,因為 MailKit 也有同名的 SmtpClient 。

  • 👀 MailKit 說明 : GitHub - MailKit

  • 👀 MimeKit 說明 : GitHub - MimeKit

14. 測試寄信功能信箱是不是都能收到信,完成~

https://ithelp.ithome.com.tw/upload/images/20210917/20139487jo0dT8C1Fa.jpg



本日總結 :

📢 今天實作的下拉選單是自己取資料的作法,裡面的國家分類沒有要做特殊處理,其實可以用頁面設計精靈製作,在 HTML 頁面修改就可以達到相同效果,不用在後置程式碼自己寫,之後的文章會介紹如何使用精靈綁定資料庫資料。

  • 明日將介紹如何製作後台管理者管理頁面及密碼加密。

上一篇
Day 2 - Using Google reCAPTCHA with ASP.NET Web Forms C#「我不是機器人」驗證
下一篇
Day 4 - Using Argon2 for Salted Password Hashing with ASP.NET Web Forms C# 使用 Argon2 替密碼加鹽後雜湊加密
系列文
ASP.NET Web Forms 入門 - 30天建立遊艇網頁專案後端及後台功能 C#30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言