首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# ASP.NET 實現網頁發送郵件:前端 jQuery + 後端 Generic Handler + SMTP 封裝完整範例

     編輯:關於C語言

在傳統 ASP.NET WebForms / MVC 專案中,實現「網頁填寫表單 → 非同步發送郵件 → 即時回饋」是常見需求。本文提供一個零依賴、可直接複製執行的完整範例,涵蓋前端頁面、非同步處理器、SMTP 封裝類三層架構,並補充生產環境的安全與效能強化建議。

架構總覽

┌─────────────┐     HTTP POST (AJAX)      ┌──────────────────┐     SMTP      ┌────────────┐
│  前端頁面   │ ─────────────────────────► │  Handler.ashx    │ ────────────► │ SMTP Server │
│ (HTML + JS) │ ◄───────────────────────── │ (Generic Handler)│               │ (Gmail/自建) │
└─────────────┘      JSON Response        └──────────────────┘               └────────────┘
      ▲                                                  │
      │                    EmailClient Helper             │
      └──────────────────────────────────────────────────┘

第一層:前端頁面 (SendMail.aspx / .html)

<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>網頁發送郵件測試</title>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <style>
        body { font-family: '微軟正黑體', sans-serif; max-width: 500px; margin: 2rem auto; }
        .form-group { margin-bottom: 1rem; display: flex; gap: 0.5rem; align-items: center; }
        label { width: 80px; text-align: right; }
        input, textarea { flex: 1; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; }
        button { margin-left: 80px; padding: 0.5rem 1.5rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
        button:disabled { background: #6c757d; cursor: not-allowed; }
        .msg { margin-left: 80px; padding: 0.5rem; border-radius: 4px; display: none; }
        .success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
        .error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
    </style>
</head>
<body>
    <h2>SMTP 郵件發送測試</h2>
    
    <div class="form-group">
        <label>SMTP 伺服器</label>
        <input type="text" id="txtSmtp" placeholder="smtp.gmail.com" value="smtp.gmail.com" />
    </div>
    <div class="form-group">
        <label>寄件人</label>
        <input type="email" id="txtFrom" placeholder="sender@gmail.com" />
    </div>
    <div class="form-group">
        <label>收件人</label>
        <input type="email" id="txtTo" placeholder="receiver@example.com" />
    </div>
    <div class="form-group">
        <label>主旨</label>
        <input type="text" id="txtTitle" placeholder="測試郵件主旨" />
    </div>
    <div class="form-group">
        <label>內容</label>
        <textarea id="txtContent" rows="4" placeholder="郵件內容..."></textarea>
    </div>
    <div class="form-group">
        <button id="btnSend" type="button">發送郵件</button>
        <div id="msg" class="msg"></div>
    </div>

    <script>
        $(function () {
            $('#btnSend').click(function () {
                var $btn = $(this).prop('disabled', true);
                $('#msg').hide().removeClass('success error');

                $.post('Handler.ashx', {
                    smtp: $('#txtSmtp').val().trim(),
                    from: $('#txtFrom').val().trim(),
                    to: $('#txtTo').val().trim(),
                    title: $('#txtTitle').val().trim(),
                    content: $('#txtContent').val().trim()
                })
                .done(function (resp) {
                    // Handler 回傳 JSON: { success: true, message: "..." }
                    var data = typeof resp === 'string' ? JSON.parse(resp) : resp;
                    showMsg(data.message, data.success);
                })
                .fail(function (xhr) {
                    showMsg('請求失敗: ' + xhr.statusText, false);
                })
                .always(function () {
                    $btn.prop('disabled', false);
                });
            });

            function showMsg(text, isSuccess) {
                $('#msg').text(text).addClass(isSuccess ? 'success' : 'error').show();
            }
        });
    </script>
</body>
</html>

第二層:非同步處理器

<%@ WebHandler Language="C#" Class="Handler" %>

using System;
using System.Web;
using System.Web.Script.Serialization;
using System.Collections.Generic;

public class Handler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "application/json; charset=utf-8";
        
        // 1. 取得並驗證參數
        string smtp = context.Request.Form["smtp"];
        string from = context.Request.Form["from"];
        string to = context.Request.Form["to"];
        string subject = context.Request.Form["title"];
        string body = context.Request.Form["content"];

        var resp = new Dictionary<string, object>();

        if (string.IsNullOrWhiteSpace(smtp) || string.IsNullOrWhiteSpace(from) ||
            string.IsNullOrWhiteSpace(to) || string.IsNullOrWhiteSpace(subject))
        {
            resp["success"] = false;
            resp["message"] = "SMTP、寄件人、收件人、主旨皆為必填";
            WriteJson(context, resp);
            return;
        }

        try
        {
            // 2. 呼叫 SMTP 封裝類發送
            var client = new EmailClient(smtp);
            client.SendEmail(from, to, subject, body);

            resp["success"] = true;
            resp["message"] = "郵件發送成功!";
        }
        catch (Exception ex)
        {
            resp["success"] = false;
            resp["message"] = "發送失敗: " + ex.Message;
            // 實務上應記錄完整堆疊至 Log: Log.Error(ex, "SendMail failed");
        }

        WriteJson(context, resp);
    }

    private void WriteJson(HttpContext context, Dictionary<string, object> data)
    {
        var json = new JavaScriptSerializer().Serialize(data);
        context.Response.Write(json);
    }

    public bool IsReusable => false;
}

第三層:SMTP 封裝類

using System.Net.Mail;
using System.Net;

public class EmailClient
{
    private readonly string _smtpHost;
    private readonly int _smtpPort;
    private readonly string _userName;
    private readonly string _password;
    private readonly bool _enableSsl;

    // 建構子支援 "host:port" 或單純 host
    public EmailClient(string smtpServer, string userName = null, string password = null, bool enableSsl = true, int port = 587)
    {
        if (string.IsNullOrWhiteSpace(smtpServer)) throw new ArgumentException("SMTP 伺服器不可為空");
        
        var parts = smtpServer.Split(':');
        _smtpHost = parts[0];
        _smtpPort = parts.Length > 1 && int.TryParse(parts[1], out int p) ? p : port;
        _userName = userName;
        _password = password;
        _enableSsl = enableSsl;
    }

    public void SendEmail(string from, string to, string subject, string body, bool isBodyHtml = false)
    {
        using (var message = new MailMessage())
        using (var smtp = new SmtpClient(_smtpHost, _smtpPort))
        {
            message.From = new MailAddress(from);
            message.To.Add(to);
            message.Subject = subject;
            message.Body = body;
            message.IsBodyHtml = isBodyHtml;
            message.BodyEncoding = System.Text.Encoding.UTF8;
            message.SubjectEncoding = System.Text.Encoding.UTF8;

            smtp.EnableSsl = _enableSsl;
            smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
            smtp.UseDefaultCredentials = false;
            
            if (!string.IsNullOrEmpty(_userName))
                smtp.Credentials = new NetworkCredential(_userName, _password);
            // 若無帳密則嘗試匿名發送 (視 SMTP 伺服器設定)

            smtp.Send(message); // 同步發送;高併發建議改 smtp.SendMailAsync
        }
    }

    // 非同步版本 (ASP.NET 4.5+ / .NET Core 建議使用)
    public async Task SendEmailAsync(string from, string to, string subject, string body, bool isBodyHtml = false)
    {
        using (var message = new MailMessage())
        using (var smtp = new SmtpClient(_smtpHost, _smtpPort))
        {
            message.From = new MailAddress(from);
            message.To.Add(to);
            message.Subject = subject;
            message.Body = body;
            message.IsBodyHtml = isBodyHtml;
            message.BodyEncoding = System.Text.Encoding.UTF8;

            smtp.EnableSsl = _enableSsl;
            smtp.UseDefaultCredentials = false;
            if (!string.IsNullOrEmpty(_userName))
                smtp.Credentials = new NetworkCredential(_userName, _password);

            await smtp.SendMailAsync(message);
        }
    }
}

生產環境必備強化清單

項目風險對策
明文傳輸帳密中間人攻擊竊取憑證強制 EnableSsl=true、使用 Port 587 (STARTTLS) 或 465 (SMTPS)
無輸入驗證Header Injection (CRLF 注入)驗證 Email 格式、過濾換行符、使用 MailAddress 建構器自動編碼
同步阻塞執行緒高併發時 Thread Pool 耗盡改用 SendMailAsync + async/await、或掛載背景工作佇列
無重試機制暫時性網路錯誤導致發送失敗引入 Polly 重試策略:WaitAndRetry(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))
無發送紀錄/追蹤無法稽核、除錯困難寫入 DB/Log:MessageId、收件人、狀態、錯誤碼、時間戳
大量發送觸發封鎖IP 被列入黑名單實作節流、使用專業郵件服務 (SendGrid, Mailgun, Amazon SES)

常見 SMTP 服務商設定參考

// Gmail (需開啟「低安全性應用程式存取」或使用應用程式密碼)
new EmailClient("smtp.gmail.com", "your@gmail.com", "app-password", true, 587);

// Outlook / Office 365
new EmailClient("smtp.office365.com", "user@domain.com", "password", true, 587);

// 企業內部 Exchange (通常無需 SSL、匿名中繼)
new EmailClient("mail.corp.internal", null, null, false, 25);

// SendGrid / Mailgun (API Key 做為密碼)
new EmailClient("smtp.sendgrid.net", "apikey", "SG.xxxx", true, 587);

進階擴充:支援附件、HTML 版面、內嵌圖片

public void SendEmailWithAttachment(string from, string to, string subject, string htmlBody, 
    IEnumerable<string> attachmentPaths = null, string plainTextBody = null)
{
    using (var message = new MailMessage())
    {
        // ... 設定 From/To/Subject ...
        
        // 替代檢視:純文字 + HTML 雙版本
        if (!string.IsNullOrEmpty(plainTextBody))
            message.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(plainTextBody, null, "text/plain"));
        message.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(htmlBody, null, "text/html"));

        // 附件
        if (attachmentPaths != null)
            foreach (var path in attachmentPaths)
                if (File.Exists(path))
                    message.Attachments.Add(new Attachment(path));

        // 內嵌圖片 (HTML 中用 <img src="cid:logo" />)
        var logo = new LinkedResource("logo.png", "image/png") { ContentId = "logo" };
        message.AlternateViews[1].LinkedResources.Add(logo);

        // 發送...
    }
}

這個三層架構範例展示了 ASP.NET 處理郵件發送的標準模式。實務上建議將 EmailClient 註冊為 DI 服務、搭配 IEmailSender 介面、使用 appsettings.json 管理 SMTP 設定、引入背景工作佇列 (Hangfire/BackgroundService) 實現真正的非同步解耦,打造可觀測、可擴充、生產就緒的郵件發送子系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved