分享程式代碼相關筆記
目前文章總數:172 篇
最後更新:2025年 03月 22日
查詢自己的 Email 可能會出現如下叉燒包的圖片,這是因為裡面用的是 URL 連結
若想要讓自己的 Image Url 伺服器變更後,不影響原本已送出的 Email 內容,可以採用內嵌附件的方法解決。
打開範例代碼後,架構基本分成以下:
1. Model | : | 檢視模型、API介接 Data Transfer object |
2. Service | : | 發送郵件的方法 |
3. Web控制器 | : | 提供發送郵件的頁面檢視、API 發送接口 |
4. Html 畫面 | : | Html 使用者操作畫面 |
5. 初始化配置 | : | 基本的依賴注入 |
用於 API 傳遞發送 Mail 所需參數
public class EmailDTO
{
public string SmtpServer { get; set; }
public int SmtpPort { get; set; }
public string SenderEmail { get; set; }
public string SenderPassword { get; set; }
public string RecipientEmail { get; set; }
public string Subject { get; set; }
}
以及畫面上 FromBody 對應的參數
public class EmailViewModel
{
[Required(ErrorMessage = "請輸入 SMTP 伺服器")]
public string SmtpServer { get; set; }
[Required(ErrorMessage = "請輸入 SMTP 連接埠")]
[Range(1, 65535, ErrorMessage = "連接埠必須介於 1-65535 之間")]
public int SmtpPort { get; set; }
[Required(ErrorMessage = "請輸入寄件者信箱")]
[EmailAddress(ErrorMessage = "請輸入有效的電子郵件地址")]
public string SenderEmail { get; set; }
[Required(ErrorMessage = "請輸入寄件者密碼")]
public string SenderPassword { get; set; }
[Required(ErrorMessage = "請輸入收件者信箱")]
[EmailAddress(ErrorMessage = "請輸入有效的電子郵件地址")]
public string RecipientEmail { get; set; }
[Required(ErrorMessage = "請輸入郵件主旨")]
public string Subject { get; set; }
}
代碼依序有 4 個步驟,其中 2-4. 會將取得的圖片轉換為 CID ,在 Email 中以附件檔案的方式提供 Body 呼叫
public class SendEmailService : ISendEmailService
{
/// <summary>
/// 使用附件方式功能,發送郵件
/// </summary>
public async Task<string> SendEmail(EmailDTO emailDto)
{
try
{
// 1. 輸入自己的信箱密碼 - 這個要輸入自己安全應用程式上的產生密碼
string senderPassword = emailDto.SenderPassword;
// 2-1. 建立 MailMessage
MailMessage mail = new MailMessage
{
From = new MailAddress(emailDto.SenderEmail),
Subject = "個人資料 - 附帶圖片",
IsBodyHtml = true,
};
// 2-2. 郵件副本對象
mail.To.Add(emailDto.RecipientEmail);
// 2-3. 可將圖片網址做為傳參
var imageBytes = await DownloadImageAsync();
// 2-4. 將圖片存成附件,讓 Mail 中,不依賴 Url 而是存在 Mail 中
// 優點:未來Url失效時,此郵件仍可檢閱圖片
var imageStream = new MemoryStream(imageBytes);
var inlineImage = new Attachment(imageStream, "louis.jpg", "image/jpeg");
// 關鍵:設定,並讓這個 Content-ID 用於 HTML 內嵌
inlineImage.ContentId = "MyImage";
inlineImage.ContentDisposition.Inline = true;
inlineImage.ContentDisposition.DispositionType = DispositionTypeNames.Inline;
mail.Attachments.Add(inlineImage);
// 2-5. 撰寫 HTML 內容,並且使用 <img> 內嵌圖片
mail.Body = @"
<html>
<body>
<p>Dear 先生:</p>
<p>你好,我是 XXX (這邊附上 Attachments 圖片)</p>
<img src=""cid:MyImage"" width=""300"" alt=""個人照片"" />
<p>這是我的個人資料 ....</p>
<br />
<p>Thanks, have a good day.</p>
</body>
</html>";
// 3. 設定 SMTP 用戶端
SmtpClient smtpClient = new SmtpClient(emailDto.SmtpServer, emailDto.SmtpPort)
{
Credentials = new NetworkCredential(emailDto.SenderEmail, senderPassword),
EnableSsl = true
};
// 4. 發送郵件
smtpClient.Send(mail);
return "郵件發送成功!";
}
catch (Exception ex)
{
return $@"郵件發送失敗:{ex.Message}";
}
}
/// <summary>
/// 下載網路圖片
/// </summary>
private static async Task<byte[]> DownloadImageAsync(
string url = "https://gotoa1234.github.io/assets/image/ContinuousDeployment/docker/2025_03_08/005.png")
{
using (HttpClient client = new HttpClient())
{
try
{
var getResult = await client.GetByteArrayAsync(url);
if (getResult == null ||
getResult.Length == 0)
{
throw new Exception("圖片下載失敗!");
}
return getResult;
}
catch (Exception ex)
{
throw new Exception("圖片下載錯誤:" + ex.Message);
}
}
}
}
控制器只存在 2 個功能 : 檢視與API
public IActionResult Index()
{
return View();
}
[HttpPost]
public async Task<IActionResult> SendEmail([FromBody] EmailDTO model)
{
try
{
var result = await _sendEmailService.SendEmail(model);
return Ok(
new { success = true, message = result });
}
catch (Exception ex)
{
return BadRequest(new { success = false, message = ex.Message });
}
}
發送郵件所需的 6 項參數,為必填資料,否則無法成功透過程式發送 Email
@model SendGoogleEmailCIDWithAttachementsExample.Models.EmailViewModel
@{
ViewData["Title"] = "發送郵件範例";
}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="text-center">發送郵件</h3>
</div>
<div class="card-body">
<form id="emailForm">
<div class="mb-3">
<label for="smtpServer" class="form-label">SMTP 伺服器</label>
<input type="text" class="form-control" id="smtpServer" name="SmtpServer" value="smtp.gmail.com" required>
</div>
<div class="mb-3">
<label for="smtpPort" class="form-label">SMTP 連接埠</label>
<input type="number" class="form-control" id="smtpPort" name="SmtpPort" value="587" required>
<small class="text-muted">常用連接埠: 465 (SSL) / 587 (TLS)</small>
</div>
<div class="mb-3">
<label for="senderEmail" class="form-label">寄件者信箱</label>
<input type="email" class="form-control" id="senderEmail" name="SenderEmail" value="cap8826@gmail.com" required>
</div>
<div class="mb-3">
<label for="senderPassword" class="form-label">寄件者密碼</label>
<input type="password" class="form-control" id="senderPassword" name="SenderPassword" value="1234567890" required>
</div>
<div class="mb-3">
<label for="recipientEmail" class="form-label">收件者信箱</label>
<input type="email" class="form-control" id="recipientEmail" name="RecipientEmail" value="cap8825@gmail.com" required>
</div>
<div class="mb-3">
<label for="subject" class="form-label">郵件主旨</label>
<input type="text" class="form-control" id="subject" value="個人資料 - 附帶圖片" name="Subject" required>
</div>
<div class="d-grid">
<button type="button" id="sendButton" class="btn btn-primary">發送郵件</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- 顯示結果的模態框 -->
<div class="modal fade" id="resultModal" tabindex="-1" aria-labelledby="resultModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="resultModalLabel">郵件發送結果</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="resultMessage">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">關閉</button>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
$(document).ready(function () {
$('#sendButton').on('click', function () {
// 顯示載入中的按鈕狀態
const button = $(this);
const originalText = button.text();
button.prop('disabled', true);
button.html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 處理中...');
// 獲取表單數據
const formData = {
smtpServer: $('#smtpServer').val(),
smtpPort: parseInt($('#smtpPort').val()),
senderEmail: $('#senderEmail').val(),
senderPassword: $('#senderPassword').val(),
recipientEmail: $('#recipientEmail').val(),
subject: $('#subject').val(),
body: $('#body').val()
};
// 呼叫後端 API
$.ajax({
url: '/Home/SendEmail',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function (response) {
$('#resultMessage').html('<div class="alert alert-success">郵件發送成功!</div>');
$('#resultModal').modal('show');
// 清空表單的主旨和內容
$('#subject').val('');
$('#body').val('');
},
error: function (xhr, status, error) {
let errorMessage = '郵件發送失敗。';
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage += '<br>詳細錯誤: ' + xhr.responseJSON.message;
}
$('#resultMessage').html('<div class="alert alert-danger">' + errorMessage + '</div>');
$('#resultModal').modal('show');
},
complete: function () {
// 恢復按鈕狀態
button.prop('disabled', false);
button.text(originalText);
}
});
});
});
</script>
}
在 Asp.net Core 中添加以下注入:
builder.Services.AddScoped<ISendEmailService, SendEmailService>();
若要從程式中發送自己的 Google Email ,需要啟用應用程式密碼配置
也可參考此篇:0026. log4net 發送Email的方法,使用Gmail為範例
輸入一個名稱,自己知道用途,後續不使用可以進行刪除
系統會為您的 Google 帳戶產生一組密碼,此密碼可以用於程式碼的發送 Email 郵件
範例代碼啟動後,輸入相關資訊,並且發送
產生的 Email 中,可以正確的使用 Email 中的附件圖片,而非 URL,未來即使自己架設的 Image Server 變動,也不會影響已發送的 Email