使用 Microsoft Graph SDK 从 OneDrive 下载大文件大文件、Graph、Microsoft、OneDrive

2023-09-06 17:12:40 作者:弹断琴弦诉不尽离殇

我正在尝试使用以下 Microsoft Graph 调用从 OneDrive 下载文件:

I am trying to download files from OneDrive using below Microsoft Graph call:

using (var strm = await client.Drives[RemoteDriveId].Items[Id].Content.Request().GetAsync())
{
   byte[] byteBuffer = new byte[4096];
   filePath = System.IO.Path.Combine(folderPath, filename);
   using (System.IO.FileStream output = new FileStream(filePath, FileMode.Create))
   {
      int bytesRead = 0;
      do
      {
        bytesRead = contentStream.Read(byteBuffer, 0, byteBuffer.Length);
        if (bytesRead > 0)
        {
           output.Write(byteBuffer, 0, bytesRead);
        }
      }
      while (bytesRead > 0);
   }
}

上面代码的问题是,如果文件很大或者网络很慢,在文件完全下载之前SDK中就会抛出请求超时异常.我想分块下载文件或增加超时.如何使用 Microsoft graph SDK 实现这一点?

The issue with above code is that if the file size is large or network is slow, request time out exception is thrown in the SDK before the file is completely downloaded. I would like to download the file in chunks or increase the timeout. How can I achieve this using Microsoft graph SDK?

推荐答案

您需要使用 Range 标头对下载进行分块.

You'll need to chunk the download with the Range header.

// Based on question by Pavan Tiwari, 11/26/2012, and answer by Simon Mourier
// https://stackoverflow.com/questions/13566302/download-large-file-in-small-chunks-in-c-sharp

const long DefaultChunkSize = 50 * 1024; // 50 KB, TODO: change chunk size to make it realistic for a large file.
long ChunkSize = DefaultChunkSize;
long offset = 0;         // cursor location for updating the Range header.
byte[] bytesInStream;                    // bytes in range returned by chunk download.

// Get the collection of drive items. We'll only use one.
IDriveItemChildrenCollectionPage driveItems = await graphClient.Me.Drive.Root.Children.Request().GetAsync();

foreach (var item in driveItems)
{
    // Let's download the first file we get in the response.
    if (item.File != null)
    {
        // We'll use the file metadata to determine size and the name of the downloaded file
        // and to get the download URL.
        var driveItemInfo = await graphClient.Me.Drive.Items[item.Id].Request().GetAsync();

        // Get the download URL. This URL is preauthenticated and has a short TTL.
        object downloadUrl;
        driveItemInfo.AdditionalData.TryGetValue("@microsoft.graph.downloadUrl", out downloadUrl);

        // Get the number of bytes to download. calculate the number of chunks and determine
        // the last chunk size.
        long size = (long)driveItemInfo.Size;
        int numberOfChunks = Convert.ToInt32(size / DefaultChunkSize); 
        // We are incrementing the offset cursor after writing the response stream to a file after each chunk. 
        // Subtracting one since the size is 1 based, and the range is 0 base. There should be a better way to do
        // this but I haven't spent the time on that.
        int lastChunkSize = Convert.ToInt32(size % DefaultChunkSize) - numberOfChunks - 1; 
        if (lastChunkSize > 0) { numberOfChunks++; }

        // Create a file stream to contain the downloaded file.
        using (FileStream fileStream = System.IO.File.Create((@"C:Temp" + driveItemInfo.Name)))
        {
            for (int i = 0; i < numberOfChunks; i++)
            {
                // Setup the last chunk to request. This will be called at the end of this loop.
                if (i == numberOfChunks - 1)
                {
                    ChunkSize = lastChunkSize;
                }

                // Create the request message with the download URL and Range header.
                HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, (string)downloadUrl);
                req.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(offset, ChunkSize + offset);

                // We can use the the client library to send this although it does add an authentication cost.
                // HttpResponseMessage response = await graphClient.HttpProvider.SendAsync(req);
                // Since the download URL is preauthenticated, and we aren't deserializing objects, 
                // we'd be better to make the request with HttpClient.
                var client = new HttpClient();
                HttpResponseMessage response = await client.SendAsync(req);

                using (Stream responseStream = await response.Content.ReadAsStreamAsync())
                {
                    bytesInStream = new byte[ChunkSize];
                    int read;
                    do
                    {
                        read = responseStream.Read(bytesInStream, 0, (int)bytesInStream.Length);
                        if (read > 0)
                            fileStream.Write(bytesInStream, 0, bytesInStream.Length);
                    }
                    while (read > 0);
                }
                offset += ChunkSize + 1; // Move the offset cursor to the next chunk.
            }
        }
        return;
    }
}