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", "[email protected]"));
    //设定收信地址 ("收信人", "收信 email")
    message.To.Add(new MailboxAddress(Name.Text.Trim(), Email.Text.Trim()));
    //寄件副本email
    message.Cc.Add(new MailboxAddress("收信人名称", "[email protected]"));
    //设定优先权
    //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("[email protected]", "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 16 - Array Methods

>>:  Proxmox VE 网路基本设定

HTTP Method(HTTP动词/HTTP方法)10/28更新

因为自己常记不住HTTP的几个动词所以自己来这边写一下 因为目前还没做出自己的网出来所以会偏向在IT...

大共享时代系列_015_旅行进行式

啓程的原因? 关於让你出发去旅行的原因是什麽呢? 人际关系交流?家庭工作上的安排?个人的探索?还是其...

DAY15 在虚拟机上运行Android Studio 的第一个程序

从第三天灌好程序之後,也是过了快两个礼拜才使用他,想必每个夜晚他在硬碟里偷偷哭泣,让你的硬碟很潮,比...

曝露系数(Exposure factor)

-简单的定量风险分析 曝露系数 (EF) 曝露系数 (EF) 是在实现特定威胁时对特定资产的主观、...

我们的基因体时代-AI, Data和生物资讯 Day29-大数据追踪COVID病毒之演进

上一篇我们的基因体时代-AI, Data和生物资讯 Day28-COVID大数据:资料哪里来 开始进...