ASP.NET Core Web API中使用Microsoft Graph createUploadSession传大文件遇损坏
问题描述
我正在开发ASP.NET Core Web API,需要向SharePoint驱动器上传4MB以上的大文件。经调研使用Microsoft Graph的createUploadSession端点:
https://graph.microsoft.com/v1.0/me/drive/root:/{fileName}:/createUploadSession
该端点支持大文件上传,但使用Graph API 5.50.0版本时,虽能成功创建UploadSession并完成上传,但仅txt等小文件可正常打开,图片及更大文件上传后会损坏。以下是我的代码:
public async Task UploadFileInChunks(string uploadUrl, Stream fileStream) { const int maxChunkSize = 320 * 1024; // 320KB var buffer = new byte[maxChunkSize]; long fileLength = fileStream.Length; long totalBytesRead = 0; int bytesRead; while ((bytesRead = await fileStream.ReadAsync(buffer, 0, maxChunkSize)) > 0) { var chunkData = new ByteArrayContent(buffer, 0, bytesRead); chunkData.Headers.ContentRange = new ContentRangeHeaderValue(totalBytesRead, totalBytesRead + bytesRead - 1, fileLength); using (var requestMessage = new HttpRequestMessage(HttpMethod.Put, uploadUrl)) { requestMessage.Content = chunkData; var response = await new HttpClient().SendAsync(requestMessage); response.EnsureSuccessStatusCode(); } totalBytesRead += bytesRead; } } public async Task<string?> CreateDriveItem(FileDTO file) { var fileBytes = Convert.FromBase64String(file.Base64); var fileNameWithExtension = file.FileName + file.FileType; var requestBody = new CreateUploadSessionPostRequestBody { Item = new DriveItemUploadableProperties { Name = fileNameWithExtension, } }; var uploadSession = await _graphClient.Graph.Drives[_driveId].Root.ItemWithPath(fileNameWithExtension).CreateUploadSession.PostAsync(requestBody); if (uploadSession == null) { return null; } using (var stream = new MemoryStream(fileBytes)) { await UploadFileInChunks(uploadSession.UploadUrl ?? "", stream); } return "File uploaded successfully."; }
解决方案
文件损坏的核心问题有两个:
- 每次创建新HttpClient实例:循环内反复创建
HttpClient会导致连接池耗尽,引发请求异常或数据传输不完整。 - 未处理上传会话的响应状态:除最后一块外,大部分分块上传的响应是
202 Accepted,需根据响应头中的剩余上传范围更新已上传字节数,而非单纯累加读取的字节数。
修改后的代码
首先优化UploadFileInChunks方法,复用HttpClient并正确处理响应:
// 复用HttpClient,不要在循环内创建实例 private readonly HttpClient _httpClient = new HttpClient(); public async Task UploadFileInChunks(string uploadUrl, Stream fileStream) { const int maxChunkSize = 320 * 1024; // 320KB,需为320KB的整数倍(Microsoft Graph要求) var buffer = new byte[maxChunkSize]; long fileLength = fileStream.Length; long totalBytesUploaded = 0; while (totalBytesUploaded < fileLength) { var bytesToRead = (int)Math.Min(maxChunkSize, fileLength - totalBytesUploaded); int bytesRead = await fileStream.ReadAsync(buffer, 0, bytesToRead); if (bytesRead == 0) break; var chunkData = new ByteArrayContent(buffer, 0, bytesRead); chunkData.Headers.ContentRange = new ContentRangeHeaderValue( totalBytesUploaded, totalBytesUploaded + bytesRead - 1, fileLength ); // 明确设置内容类型,避免默认类型导致的解析问题 chunkData.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); using (var requestMessage = new HttpRequestMessage(HttpMethod.Put, uploadUrl)) { requestMessage.Content = chunkData; var response = await _httpClient.SendAsync(requestMessage); if (response.StatusCode == HttpStatusCode.Accepted) { // 解析响应头中的已完成范围,更新已上传字节数 var rangeHeader = response.Content.Headers.ContentRange; if (rangeHeader != null && rangeHeader.Ranges.Any()) { totalBytesUploaded = rangeHeader.Ranges.First().To.Value + 1; } } else if (response.StatusCode == HttpStatusCode.Created) { // 最后一块上传完成,退出循环 break; } else { response.EnsureSuccessStatusCode(); } } } }
检查Base64转换逻辑
确保传入的file.Base64是纯Base64字符串,若包含如data:image/png;base64,的前缀,需先去除:
public async Task<string?> CreateDriveItem(FileDTO file) { // 处理可能的Base64前缀 var base64Data = file.Base64; if (base64Data.Contains(",")) { base64Data = base64Data.Split(',')[1]; } var fileBytes = Convert.FromBase64String(base64Data); var fileNameWithExtension = file.FileName + file.FileType; var requestBody = new CreateUploadSessionPostRequestBody { Item = new DriveItemUploadableProperties { Name = fileNameWithExtension, } }; var uploadSession = await _graphClient.Graph.Drives[_driveId].Root.ItemWithPath(fileNameWithExtension).CreateUploadSession.PostAsync(requestBody); if (uploadSession == null) { return null; } using (var stream = new MemoryStream(fileBytes)) { await UploadFileInChunks(uploadSession.UploadUrl ?? "", stream); } return "File uploaded successfully."; }
补充说明
- Microsoft Graph分块上传要求每个块的大小必须是320KB的整数倍(最后一块除外),当前设置符合要求。
- 复用
HttpClient是避免连接问题的关键,频繁创建销毁实例会导致网络不稳定。 - 根据响应的
Content-Range更新已上传字节数,可处理网络波动导致的重传场景,避免范围错误引发文件损坏。
内容的提问来源于stack exchange,提问作者Bruno Bispo
相关产品推荐
相关产品推荐

