【问题标题】:Download large files from OneDrive using Microsoft Graph SDK使用 Microsoft Graph SDK 从 OneDrive 下载大文件
【发布时间】:2017-06-09 07:18:13
【问题描述】:

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

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 实现这一点?

【问题讨论】:

    标签: office365 microsoft-graph-api onedrive


    【解决方案1】:

    上面提供的代码包含错误

    if (read > 0)
       fileStream.Write(bytesInStream, 0, bytesInStream.Length);
    

    这是不正确的,应该是:

    if (read > 0)
       fileStream.Write(bytesInStream, 0, read);
    

    而且有点笨拙

    int lastChunkSize = Convert.ToInt32(size % DefaultChunkSize) - numberOfChunks - 1;
    

    我稍微重构了一下,决定在这里发布。

    正确代码

    private int DefaultChunkSize = 5 * 1024 * 1024;//5MB
    private int BufferSize = 4096;
    

    ...

    int chunkSize = DefaultChunkSize;
    long offset = 0; // cursor location for updating the Range header.                
    byte[] buffer = new byte[BufferSize];
    var driveItemInfo = await _api.Drive.Root.ItemWithPath(path).Request().GetAsync();
    object downloadUrl;
    driveItemInfo.AdditionalData.TryGetValue("@microsoft.graph.downloadUrl", out downloadUrl);
    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.                 
    int lastChunkSize = Convert.ToInt32(size % DefaultChunkSize);
    if (lastChunkSize > 0) {
     numberOfChunks++;
    }
    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 request = new HttpRequestMessage(HttpMethod.Get, (string) downloadUrl);
     //-1 because range is zero based
     request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(offset, chunkSize + offset - 1);
     // 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(request);
     int totalRead = 0;
     using(Stream responseStream = await response.Content.ReadAsStreamAsync()) {
      int read;
      while ((read = await responseStream.ReadAsync(buffer: buffer, offset: 0, count: buffer.Length)) > 0) {
       stream.Write(buffer, 0, read);
       totalRead += read;
      }
     }
     offset += totalRead; // Move the offset cursor to the next chunk.
    }
    

    【讨论】:

    • 您缺少文件流,但除此之外,此解决方案帮助了我。谢谢
    • 这似乎随着时间的推移而发生了变化。请更新行 driveItemInfo.AdditionalData.TryGetValue("@content.downloadUrl", out downloadUrl);驱动ItemInfo.AdditionalData.TryGetValue("@microsoft.graph.downloadUrl", out downloadUrl);
    • @RafaAyadi 我有以下代码: object downloadUrlJson; driveItemInfo.AdditionalData.TryGetValue("@content.downloadUrl", out downloadUrlJson); var downloadJsonElement = (System.Text.Json.JsonElement)downloadUrlJson; string downloadUrl = downloadJsonElement.GetString();
    • @RafaAyadi 谢谢你的评论,我没有检查他们的代码,但我的发明看起来不太好。
    【解决方案2】:

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

    // 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;
        }
    }
    

    【讨论】:

    • 我知道使用 SDK 上传块文件。我的问题是关于文件的下载。
    • 修正了我的答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-13
    相关资源
    最近更新 更多