使用Gmail API与ASP.NET Core Web API发邮件的本地测试问题咨询
Gmail API邮件发送POC问题解答
背景
基于ASP.NET Core Web API + Google.Apis.Gmail.v1开发邮件发送POC,因2024年9月30日后SMTP方式将被停用,转而使用Gmail API。已在Google开发者控制台创建Web应用OAuth客户端ID并启用Gmail API,本地测试时发现错误信息中的http://127.0.0.1:xxxx端口每次启动都会变化,引发OAuth重定向不匹配错误。
问题解答
1. 端口变化是否由本地测试导致?
是的,这完全是本地调试的特性。ASP.NET Core默认使用随机端口启动本地服务(除非手动配置固定端口),因此每次启动应用,http://127.0.0.1后的端口都会变化。而你在Google开发者控制台配置的OAuth重定向URI是固定值,两者不匹配就会触发错误。
2. 部署到Azure App Service能否解决该错误?
可以解决,但需要完成两个步骤:
- 部署后,Azure App Service会提供固定的应用域名/URL,把这个URL添加到Google开发者控制台的授权重定向URI列表中
- 首次部署后需要触发OAuth授权流程(访问API触发发送邮件逻辑),完成授权后令牌会存入你的数据库,后续即可正常发送邮件
3. 可行的本地测试方法
方法一:配置ASP.NET Core使用固定端口
在项目的launchSettings.json中设置固定端口:
"profiles": { "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://127.0.0.1:5000", // 指定固定端口 "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } }
然后将http://127.0.0.1:5000添加到Google开发者控制台的重定向URI列表中。
方法二:指定OAuth回调固定端口
修改GoogleWebAuthorizationBroker.AuthorizeAsync的调用,自定义本地回调端口:
var newCredential = await GoogleWebAuthorizationBroker.AuthorizeAsync( secrets, Scopes, "user", CancellationToken.None, new NullDataStore(), new LocalServerCodeReceiver(8080) // 指定固定端口8080 );
同时在Google控制台中添加http://localhost:8080作为重定向URI。
方法三:复用已有令牌跳过OAuth流程
如果已经通过线上环境或其他方式获取了有效的Gmail API访问令牌,直接将令牌存入本地数据库,测试时代码会直接读取令牌,跳过OAuth授权流程,避免端口不匹配问题。注意:令牌过期后需要重新获取。
你的代码
using GeoConnect.Clients; using GeoConnect.EmailPOC; 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 Google.Apis.Util.Store; using Microsoft.AspNetCore.Mvc; using System.Text; using System.Threading; using Volo.Abp.Application.Services; namespace GeoConnect.EmailSendingPOC; public class EmailSendingPocService : ApplicationService { private readonly string[] Scopes = { GmailService.Scope.GmailSend }; private readonly string ApplicationName = "Gmail API .NET Example"; private readonly string ClientId = ""; private readonly string ClientSecret = ""; private readonly IRepository<EmailSendingPoc, Guid> _tokenRepository; public EmailSendingPocService(IRepository<EmailSendingPoc, Guid> tokenRepository) { _tokenRepository = tokenRepository; } public async Task SendEmail(string to="test.vishnukumarps@gmail.com",string from= "test.vishnukumarps@gmail.com") { try { var credential = await GetUserCredential(); // Create Gmail API service var service = new GmailService(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = ApplicationName, }); var email = CreateEmail(to,from , "Subject", "Hello, this is a test email!"); var message = new Message { Raw = Base64UrlEncode(email) }; // Send the email await service.Users.Messages.Send(message, "me").ExecuteAsync(); } catch (Exception ex) { throw; } } private async Task<UserCredential> GetUserCredential() { var secrets = new ClientSecrets { ClientId = ClientId, ClientSecret = ClientSecret }; var token = await _tokenRepository.FirstOrDefaultAsync(); if (!string.IsNullOrEmpty(token?.Token)) { // Create a UserCredential object using the stored token var credential = new UserCredential(new GoogleAuthorizationCodeFlow( new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = secrets, Scopes = Scopes }), "user", new TokenResponse { AccessToken = token?.Token } ); return credential; } var newCredential = await GoogleWebAuthorizationBroker.AuthorizeAsync( secrets, Scopes, "user", CancellationToken.None, new NullDataStore() // No data store in this case ); // Save the token to the database // await _tokenRepository.SaveTokenAsync("user", newCredential.Token.AccessToken, newCredential.Token.Expiration); await _tokenRepository.InsertAsync(new EmailSendingPoc() { Token = newCredential.Token.AccessToken }, true); return newCredential; } private string CreateEmail(string to, string from, string subject, string body) { var message = new StringBuilder(); message.AppendLine($"To: {to}"); message.AppendLine($"From: {from}"); message.AppendLine($"Subject: {subject}"); message.AppendLine("Content-Type: text/plain; charset=utf-8"); message.AppendLine(); message.AppendLine(body); return message.ToString(); } private static string Base64UrlEncode(string input) { var bytes = Encoding.UTF8.GetBytes(input); return Convert.ToBase64String(bytes) .Replace('+', '-') .Replace('/', '_') .Replace("=", ""); } }
内容的提问来源于stack exchange,提问作者VishnuKumar ps
相关产品推荐
相关产品推荐

