调用Microsoft Graph API发送邮件遇HttpWebRequest 413错误
问题:使用Microsoft Graph API发送带大附件邮件时返回Error 413
- 环境:C# WPF应用,调用Microsoft Graph API的
/me/sendMail接口 - 现象:发送单个100KB附件正常,发送三个附件(2MB PDF + 20MB PDF + 100KB CSV)时返回413错误(请求实体过大)
- 无效尝试:设置内容长度、启用
SendChunked;找到的解决方案多涉及WCF和修改web.config,但应用无此配置文件 - 疑问:Graph文档标注附件总大小上限为150MB,当前总大小远低于该值,寻求适配WPF的有效方案
原发送代码
public void SendEmail(string messageJson, string token) { HttpWebRequest request = WebRequest.Create($"https://graph.microsoft.com/v1.0/me/sendMail") as HttpWebRequest; request.ContentType = "application/json"; request.Method = "POST"; request.Accept = ("application/json"); request.Headers.Add("Authorization", $"Bearer {token}"); string resultJSON = string.Empty; try { //Send Request using (var streamWriter = new StreamWriter(request.GetRequestStream())) { streamWriter.Write(messageJson); } using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) { using (StreamReader reader = new StreamReader(response.GetResponseStream())) { resultJSON = reader.ReadToEnd(); } } } catch (WebException e) { var response = ((HttpWebResponse)e.Response); var someheader = response.Headers["X-API-ERROR"]; // check header string content = string.Empty; // protocol errors find the statuscode in the Response // the enum statuscode can be cast to an int. int code = (int)((HttpWebResponse)e.Response).StatusCode; using (var reader = new StreamReader(e.Response.GetResponseStream())) { content = reader.ReadToEnd(); } Console.WriteLine(content); } }
解决方案:使用Graph API上传会话分块处理大附件
核心原因
直接将大附件以base64编码放入sendMail的JSON请求体中,会导致单个请求的实体体积过大,触发413错误。Graph的150MB是附件总大小上限,但单请求的payload有单独限制,因此需要用分块上传的方式先上传附件,再关联到邮件发送。
具体实现步骤
- 创建草稿邮件(不含大附件):先调用
POST /me/messages创建仅包含主题、收件人、正文的草稿邮件,暂不添加大附件。 - 为大附件创建上传会话:对20MB这类大附件,调用
POST /me/messages/{messageId}/attachments/createUploadSession接口,获取上传会话URL。 - 分块上传附件内容:使用上传会话URL,将大附件按5MB-10MB的块大小分块上传(Graph推荐范围)。
- 添加小附件到草稿:对100KB CSV、2MB PDF这类小附件,直接调用
POST /me/messages/{messageId}/attachments接口添加。 - 发送草稿邮件:调用
POST /me/messages/{messageId}/send发送已添加全附件的草稿。
关键代码片段
创建上传会话
public string CreateAttachmentUploadSession(string messageId, string attachmentName, long fileSize, string token) { var uploadSessionRequest = new { AttachmentItem = new { AttachmentType = "file", Name = attachmentName, Size = fileSize } }; var jsonContent = Newtonsoft.Json.JsonConvert.SerializeObject(uploadSessionRequest); var request = WebRequest.Create($"https://graph.microsoft.com/v1.0/me/messages/{messageId}/attachments/createUploadSession") as HttpWebRequest; request.ContentType = "application/json"; request.Method = "POST"; request.Headers.Add("Authorization", $"Bearer {token}"); using (var streamWriter = new StreamWriter(request.GetRequestStream())) { streamWriter.Write(jsonContent); } using (var response = (HttpWebResponse)request.GetResponse()) using (var reader = new StreamReader(response.GetResponseStream())) { var sessionJson = reader.ReadToEnd(); var sessionObj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(sessionJson); return sessionObj.uploadUrl; } }
分块上传文件
public void UploadFileInChunks(string uploadUrl, string filePath, int chunkSize = 5 * 1024 * 1024) { var fileInfo = new FileInfo(filePath); long fileSize = fileInfo.Length; long bytesUploaded = 0; using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { while (bytesUploaded < fileSize) { long bytesToRead = Math.Min(chunkSize, fileSize - bytesUploaded); var buffer = new byte[bytesToRead]; fileStream.Read(buffer, 0, (int)bytesToRead); var request = WebRequest.Create(uploadUrl) as HttpWebRequest; request.Method = "PUT"; request.ContentLength = bytesToRead; request.Headers.Add("Content-Range", $"bytes {bytesUploaded}-{bytesUploaded + bytesToRead - 1}/{fileSize}"); using (var requestStream = request.GetRequestStream()) { requestStream.Write(buffer, 0, buffer.Length); } using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.Created) { // 上传完成 break; } else if (response.StatusCode != HttpStatusCode.Accepted) { throw new Exception($"上传分块失败,状态码:{(int)response.StatusCode}"); } } bytesUploaded += bytesToRead; } } }
注意事项
- 分块大小建议设置为5MB-10MB,避免过小导致请求过多,过大触发单块限制
- 上传会话默认有效期24小时,需在此时间内完成上传
- 小附件可直接添加,无需分块,简化流程
内容的提问来源于stack exchange,提问作者cWilson
相关产品推荐
相关产品推荐

