[C#] #12 使用 Google Gmail API 發送電子郵件 - antqtech/KM GitHub Wiki
IGmail.cs
namespace SeeGo_API.Services.Interfaces
{
public interface IGmail
{
string SendMail(string toEmail, string subject, string htmlBodyContent);
}
}
GmailService.cs
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Services;
using MimeKit;
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using Newtonsoft.Json.Linq;
using SeeGo_API.Services.Interfaces;
namespace SeeGo_API.Services
{
public class GmailServices : IGmail
{
private string ClientId = "ClientId ";
private string ClientSecret = "ClientSecret";
public string SendMail(string toEmail, string subject, string htmlBodyContent)
{
string fromAddress = "[email protected]";
string fromDisplayName = "戶外運動整合平台";
var mailMessage = new MimeMessage();
mailMessage.From.Add(new MailboxAddress(fromDisplayName, fromAddress));
mailMessage.To.Add(new MailboxAddress("", toEmail));
mailMessage.Subject = subject;
var textPart = new TextPart("html")
{
Text = htmlBodyContent
};
textPart.ContentTransferEncoding = ContentEncoding.Base64;
mailMessage.Body = textPart;
var rawMessage = Convert.ToBase64String(Encoding.UTF8.GetBytes(mailMessage.ToString()))
.Replace('+', '-')
.Replace('/', '_')
.Replace("=", "");
var OAuth_Scopes = new string[] { GmailService.Scope.GmailCompose };
var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = ClientId,
ClientSecret = ClientSecret
},
Scopes = OAuth_Scopes
});
var token = new TokenResponse
{
AccessToken = GetAccessToken("XXXXXXXXXXXXXXXXXXXXXX"),
RefreshToken = "RRRRRRRRRRRRRRRRRRRRRRRRRRR"
};
var gmail = new Google.Apis.Gmail.v1.GmailService(new BaseClientService.Initializer
{
ApplicationName = "SeeGo",
HttpClientInitializer = new UserCredential(flow, fromAddress, token)
});
var result = gmail.Users.Messages.Send(new Message
{
Raw = rawMessage
}, "me").Execute();
return result.Id;
}
private string GetAccessToken(string refreshToken)
{
using (var wb = new WebClient())
{
var data = new NameValueCollection
{
["refresh_token"] = refreshToken,
["client_id"] = ClientId,
["client_secret"] = ClientSecret,
["grant_type"] = "refresh_token"
};
var responseBytes = wb.UploadValues("https://accounts.google.com/o/oauth2/token", "POST", data);
var tokens = Encoding.UTF8.GetString(responseBytes);
var json = JObject.Parse(tokens);
return json["access_token"].ToString();
}
}
}
}
GmailService解釋
程式碼定義了一個名為 GmailServices
的服務類別,用於通過 Google Gmail API 發送電子郵件。這個類別實現了 IGmail
介面,並通過 OAuth 2.0 認證流程進行 Google 帳戶授權以發送郵件。
會取名為 GmailServices
而不是 GmailService
,是因為Gmail函式庫裡面有同名的Class。
1. 成員變數
private string ClientId = "123456-XXXX.apps.googleusercontent.com";
private string ClientSecret = "Gsssssssss";
這些變數是 Google API 的客戶端 ID (ClientId
) 和客戶端密鑰 (ClientSecret
),用於 OAuth 認證流程中授權此應用程式進行 API 請求。
SendMail
方法
2. SendMail
方法是該類別的主要方法,用於發送電子郵件,並接受三個參數:
toEmail
:收件人的電子郵件地址。subject
:郵件主題。htmlBodyContent
:郵件的 HTML 內容。
方法步驟
- 組建郵件訊息:
- 創建
MimeMessage
對象,並設置發件人和收件人信息。 - 使用
TextPart
設置郵件的 HTML 主體內容,並將其編碼為 Base64 以符合 Gmail API 要求。
- OAuth 認證設置:
- 設置 Gmail API 的所需範圍(
GmailService.Scope.GmailCompose
)。 - 初始化
GoogleAuthorizationCodeFlow
,用於管理 OAuth 認證流程,並通過ClientSecrets
包含的ClientId
和ClientSecret
授權應用。
- 獲取 OAuth Token:
- 使用
TokenResponse
包含的AccessToken
和RefreshToken
進行 API 認證。這些 token 通常來自 OAuth 認證流程中的預先授權,用於維持授權連接。 - 調用自定義的
GetAccessToken
方法來刷新 token,確保授權有效性。
- 發送郵件:
- 使用
GmailService
初始化 API 客戶端服務,並通過Users.Messages.Send
方法將構建的郵件rawMessage
送出,這段程式碼設置了me
表示發件人為已授權的用戶。
GetAccessToken
方法
3. GetAccessToken
方法負責使用 refresh_token
向 Google OAuth API 請求新的 access_token
,以保證授權持續有效。該方法的流程如下:
- 使用
WebClient
進行 HTTP POST 請求,傳送 refresh token 和其他必需的 OAuth 參數至https://accounts.google.com/o/oauth2/token
。 - 解析響應 JSON,提取並返回
access_token
。
IEmailTemplate.cs
namespace SeeGo_API.Services.Interfaces
{
public interface IEmailTemplate
{
string GetTestOneParamEmailBody(string code);
string GetTestManyParamEmailBody(string code, string phone, string email);
}
}
EmailTemplateService.cs
using SeeGo_API.Services.Interfaces;
using System.Numerics;
namespace SeeGo_API.Services
{
public class EmailTemplateService : IEmailTemplate
{
private readonly IWebHostEnvironment _env;
public EmailTemplateService(IWebHostEnvironment env)
{
_env = env;
}
public string GetTestOneParamEmailBody(string code)
{
string projectRoot = _env.ContentRootPath;
string filePath = Path.Combine(projectRoot, "EmailTemplate", "Register.html");
string htmlTemplate = File.ReadAllText(filePath);
return htmlTemplate.Replace("{{code}}", code);
}
public string GetTestManyParamEmailBody(string code, string phone, string email)
{
string projectRoot = _env.ContentRootPath;
string filePath = Path.Combine(projectRoot, "EmailTemplate", "test.html");
string htmlTemplate = File.ReadAllText(filePath);
htmlTemplate = htmlTemplate.Replace("{{code}}", code);
htmlTemplate = htmlTemplate.Replace("{{phone}}", phone);
htmlTemplate = htmlTemplate.Replace("{{email}}", email);
return htmlTemplate;
}
}
}
EmailTemplateService解釋
程式碼定義了一個名為 EmailTemplateService
的類別,這個類別的作用是讀取 HTML 模板並插入動態數據,用於生成包含使用者資訊的電子郵件內容。該類別實現了 IEmailTemplate
介面,並使用依賴注入獲取專案的根目錄。
1. 成員變數
private readonly IWebHostEnvironment _env;
_env
是IWebHostEnvironment
類型的變數,表示當前 Web 應用的主機環境。通過依賴注入(Dependency Injection)傳入該環境變數,程式可以獲取應用程式的根目錄路徑,以便定位並讀取 HTML 模板文件。如果沒有這個就沒辦法找到模板。
GetTestOneParamEmailBody
方法
2. public string GetTestOneParamEmailBody(string code)
{
string projectRoot = _env.ContentRootPath;
string filePath = Path.Combine(projectRoot, "EmailTemplate", "Register.html");
string htmlTemplate = File.ReadAllText(filePath);
return htmlTemplate.Replace("{{code}}", code);
}
- 這個方法用於生成單個參數的電子郵件模板,接受一個
code
字串參數。 - 運作步驟:
- 使用
_env.ContentRootPath
獲取專案的根目錄路徑。 - 使用
Path.Combine
方法生成Register.html
的完整路徑。 - 使用
File.ReadAllText
方法讀取該 HTML 模板文件。 - 使用
Replace("{{code}}", code)
將模板中的{{code}}
標記替換為實際的code
值,並返回最終的 HTML 字串。
- 使用
GetTestManyParamEmailBody
方法
3. public string GetTestManyParamEmailBody(string code, string phone, string email)
{
string projectRoot = _env.ContentRootPath;
string filePath = Path.Combine(projectRoot, "EmailTemplate", "test.html");
string htmlTemplate = File.ReadAllText(filePath);
htmlTemplate = htmlTemplate.Replace("{{code}}", code);
htmlTemplate = htmlTemplate.Replace("{{phone}}", phone);
htmlTemplate = htmlTemplate.Replace("{{email}}", email);
return htmlTemplate;
}
- 這個方法用於生成多個參數的電子郵件模板,接受三個參數:
code
、phone
和email
。 - 運作步驟與
GetTestOneParamEmailBody
類似,但在讀取test.html
模板文件後,它依次使用Replace
方法將{{code}}
、{{phone}}
和{{email}}
標記替換為相應的參數值,最終返回完整的 HTML 字串。
實際運用
Program.cs 註冊
builder.Services.AddScoped<IEmailTemplate, EmailTemplateService>();
builder.Services.AddScoped<IGmail, GmailServices>(); // Gmail庫有GmailService這個class 所以多一個s
Controller 依賴注入
namespace SeeGo_API.Controllers
{
[Route("Account")]
[ApiController]
public class AccountController : Controller
{
// ClassBaseDB
private readonly SeeGoDbConext _dbcontext;
private readonly IWebHostEnvironment _env;
private readonly IEmailTemplate _emailTemplate;
private readonly IGmail _gmail;
public AccountController(SeeGoDbConext dbcontext, IWebHostEnvironment env, IEmailTemplate emailTemplate, IGmail gmail)
{
_dbcontext = dbcontext;
_env = env;
_emailTemplate = emailTemplate;
_gmail = gmail;
}
}
實際使用
/// <include file='Docs/AccountAPIDocs.xml' path='doc/members/member[@name="電子郵件發送測試"]/*'/>
[HttpPost("TestEmail")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult TestEmail([FromBody] ReqVerifyEmail ReqVerifyEmail)
{
int VType = ReqVerifyEmail.VType;
string MID = ReqVerifyEmail.Email;
string GenerateRandomNumber()
{
Random random = new Random();
return string.Concat(Enumerable.Range(0, 6).Select(_ => random.Next(0, 9).ToString()));
}
string ranNum = GenerateRandomNumber();
int timeStamp = Convert.ToInt32(DateTime.Now.AddMinutes(10).Subtract(new DateTime(1970, 1, 1)).TotalSeconds);
string encryptedMsg = _aesCrypto.Encrypt($"{ReqVerifyEmail.Email};{ranNum};{timeStamp}");
void SendVerificationEmail(string subject, string htmlBodyContent)
{
_gmail.SendMail(ReqVerifyEmail.Email, subject, htmlBodyContent);
}
if(VType == 1)
{
SendVerificationEmail("【SeeGo測試單參數郵件】郵件測試", _emailTemplate.GetTestOneParamEmailBody());
}
if(VType == 2)
{
string code = "948794", phone = "(02) 2597-9100", email = "[email protected]";
SendVerificationEmail("【SeeGo測試多參數郵件】郵件測試", _emailTemplate.GetTestManyParamEmailBody(code, phone, email));
return _response.GenerateOkResponse(encryptedMsg);
}
return _response.GenerateOkResponse("測試郵件已發送!");
}