在C#中使用Microsoft Graph API移除草稿邮件附件的问题排查
使用Microsoft Graph API转发邮件时移除草稿附件的问题及解决方案
问题背景
在C#应用中使用Microsoft Graph API转发邮件时,尝试移除转发生成的草稿邮件附件,遭遇tooManyRetries错误,删除操作长时间挂起后抛出异常。
原代码示例
private static async Task<Message> GetDraftMessageAsync(GraphServiceClient client, EmailCreateServiceModel model, ComposeMode mode, CancellationToken cancellationToken) { // 其他模式处理逻辑 var message = await client.Users[model.OriginalEmailAddress] .Messages[model.MessageId] .CreateForward() .Request() .Header("Prefer", "IdType=\"ImmutableId\"") .PostAsync(cancellationToken) .ConfigureAwait(false); // 疑问1:能否无需二次调用直接获取带附件的草稿邮件? var messageWithAttachments = await client.Users[model.OriginalEmailAddress] .Messages[message.Id] .Request() .Expand(m => m.Attachments) .GetAsync(cancellationToken) .ConfigureAwait(false); return messageWithAttachments; } public static async Task<Message> SendEmailAsync(EmailCreateServiceModel model, ComposeMode mode, List<string> keepingAttachments, CancellationToken cancellationToken) { var client = await AuthService.GetGraphClientAsync(); var draftMessage = await GetDraftMessageAsync(client, model, mode, cancellationToken) .ConfigureAwait(false); if (mode is ComposeMode.Forward && draftMessage.Attachments != null) { var attachmentsToRemove = draftMessage.Attachments .Where(attachment => !keepingAttachments.Contains(attachment.Name)) .ToList(); foreach (var attachment in attachmentsToRemove) { // 疑问2:此代码长时间挂起后抛出异常 await client.Users[model.OriginalEmailAddress] .Messages[draftMessage.Id] .Attachments[attachment.Id] .Request() .DeleteAsync(cancellationToken) .ConfigureAwait(false); } } draftMessage.From = string.IsNullOrEmpty(model.From) ? null : new() { EmailAddress = new() { Address = model.From } }; draftMessage.ToRecipients = GetRecipients(model.To); draftMessage.CcRecipients = GetRecipients(model.Cc); draftMessage.BccRecipients = GetRecipients(model.Bcc); draftMessage.Subject = model.Subject; draftMessage.Body = new ItemBody() { Content = model.Body, ContentType = BodyType.Html, }; draftMessage = await client.Users[model.OriginalEmailAddress] .Messages[draftMessage.Id] .Request() .UpdateAsync(draftMessage, cancellationToken) .ConfigureAwait(false); return draftMessage; }
错误详情
Microsoft.Graph.ServiceException HResult=0x80131500 Message=Code: tooManyRetries Message: More than 3 retries encountered while sending the request. Source=Microsoft.Graph.Core StackTrace: at Microsoft.Graph.HttpProvider.<SendRequestAsync>d__19.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() ... at Microsoft.Graph.AttachmentRequest.<DeleteAsync>d__3.MoveNext() in AttachmentRequest.cs ...
核心疑问
- 删除草稿邮件附件是否需要特定权限?
- 获取和修改草稿邮件的方式是否存在问题?
- 如何正确处理
tooManyRetries错误?
更新信息
使用Graph Explorer执行删除草稿附件操作时,返回以下错误:
Error code: RequestBroker-ParseUri Message: Could not parse the URL. Details: Unrecognized segment 'attachments'
解决方案
1. 无需二次调用获取带附件的草稿邮件
可以直接在CreateForward请求中添加$expand参数,一次性获取包含附件的草稿邮件,避免二次请求。修改后的代码片段:
var message = await client.Users[model.OriginalEmailAddress] .Messages[model.MessageId] .CreateForward() .Request() .Header("Prefer", "IdType=\"ImmutableId\"") .Expand(m => m.Attachments) // 添加此行,直接返回附件数据 .PostAsync(cancellationToken) .ConfigureAwait(false);
2. 删除草稿附件的权限要求
删除草稿邮件附件需要Mail.ReadWrite权限,需确保应用已正确申请该权限(委托权限或应用权限均可,根据业务场景选择)。
3. 修正草稿邮件的获取与修改方式
- 避免替换整个Message对象:原代码中直接更新完整的
draftMessage对象,可能覆盖Graph API自动维护的系统字段(如LastModifiedDateTime),建议采用PATCH部分字段的方式更新,仅修改需要变更的属性。 - 避免循环逐个删除附件:循环单次删除会增加请求次数,容易触发Graph API的速率限制,导致重试失败。推荐使用批量请求合并多个删除操作,减少请求频次。
4. 处理tooManyRetries错误
- 使用批量请求删除附件:将多个删除附件的请求合并为一个批量请求发送,示例代码如下:
if (attachmentsToRemove.Any()) { var batchRequestContent = new BatchRequestContent(); foreach (var attachment in attachmentsToRemove) { var deleteRequest = client.Users[model.OriginalEmailAddress] .Messages[draftMessage.Id] .Attachments[attachment.Id] .Request() .GetHttpRequestMessage(HttpMethod.Delete); batchRequestContent.AddBatchRequestStep(deleteRequest); } var batchResponse = await client.Batch.Request().PostAsync(batchRequestContent, cancellationToken) .ConfigureAwait(false); // 检查批量请求结果,处理失败的删除操作 foreach (var stepResponse in batchResponse.BatchResponses) { if (!stepResponse.Response.IsSuccessStatusCode) { var errorContent = await stepResponse.Response.Content.ReadAsStringAsync(cancellationToken); // 记录日志或执行补偿逻辑 } } // 删除后重新获取草稿最新状态,确保后续更新基于最新数据 draftMessage = await client.Users[model.OriginalEmailAddress] .Messages[draftMessage.Id] .Request() .Expand(m => m.Attachments) .GetAsync(cancellationToken) .ConfigureAwait(false); }
- 自定义重试策略:初始化
GraphServiceClient时,可自定义重试规则,针对速率限制(429)、服务器错误(5xx)调整重试次数与间隔:
var retryHandler = new RetryHandler(new RetryHandlerOptions { MaxRetry = 5, // 增加重试次数 RetryDelay = TimeSpan.FromSeconds(2), // 调整重试间隔 ShouldRetry = (delay, attempt, message) => { // 针对429和5xx错误进行重试 return message.StatusCode == HttpStatusCode.TooManyRequests || (int)message.StatusCode >= 500; } }); var httpClient = HttpClientFactory.Create(retryHandler); var client = new GraphServiceClient(httpClient, authProvider);
5. 修正Graph Explorer的URL错误
确保删除附件的请求路径格式正确:/users/{user-id}/messages/{draft-message-id}/attachments/{attachment-id},其中draft-message-id是转发生成的草稿邮件ID,而非原邮件ID。
内容的提问来源于stack exchange,提问作者Alireza Noori
相关产品推荐
相关产品推荐

