Search code examples
c#asp.net-coremicrosoft-graph-apihttprequestonedrive

OneDrive REST API - Requested Range Error While Uploading Chunk


I've been trying to upload files to my OneDrive via HTTP Requests following this document (https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession?view=odsp-graph-online) without success. I have the following steps rounded up (Authentication, folder creation for the file, create an upload session) but when I try the last step, byte upload to the created session, I get this error in the second PUT request:

Requested Range Not Satisfiable {"error":{"code":"invalidRange","message":"Optimistic concurrency failure during fragmented upload"}}

This is my code:

//Get File Data
byte[] FileByteArray = File.ReadAllBytes(FilePath);

//Create Upload Session
OutlookEndpoint = $"{AppSettings.DriveSettings.OneDriveSettings.Endpoint}/me/drive/items/{FolderId}:/{Name}:/createuploadsession";
OutlookResponseMessage = await OutlookClient.PostAsync(OutlookEndpoint, new StringContent("{}", Encoding.UTF8, "application/json"));
OutlookResponseContent = await OutlookResponseMessage.Content.ReadAsStringAsync();

if (OutlookResponseMessage.IsSuccessStatusCode)
{
    OutlookUpload OutlookUpload = JsonConvert.DeserializeObject<OutlookUpload>(OutlookResponseContent);

    //Check the Created URL
    if (!string.IsNullOrEmpty(OutlookUpload.UploadUrl))
    {
        //Chunk Calculation
        int TotalSize = FileByteArray.Length;
        int AcumulativeSize = 0;
        int ChunkSize = 327680;
        int ChunkBuffer = ChunkSize;
        int ChunkNumber = TotalSize / ChunkSize;
        int ChunkLeftover = TotalSize - ChunkSize * ChunkNumber;
        int ChunkCounter = 0;

        while (true)
        {
            if (ChunkNumber == ChunkCounter)
            {
                ChunkSize = ChunkLeftover;
            }

            byte[] ChunkData = FileByteArray.Skip(ChunkBuffer * ChunkCounter).Take(ChunkSize).ToArray();

            AcumulativeSize += ChunkData.Length;

            //PUT Upload of Chunk
            string UploadEndpoint = OutlookUpload.UploadUrl;

            string BytesHeader = $"bytes {AcumulativeSize - ChunkSize}-{AcumulativeSize - 1}/{TotalSize}";

            OutlookClient.DefaultRequestHeaders.Clear();
            OutlookClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
            OutlookClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Length", ChunkSize.ToString());
            OutlookClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Range", BytesHeader);

            OutlookResponseMessage = await OutlookClient.PutAsync(UploadEndpoint, new ByteArrayContent(ChunkData));
            OutlookResponseContent = await OutlookResponseMessage.Content.ReadAsStringAsync();

            if (OutlookResponseMessage.IsSuccessStatusCode)
            {
                Console.WriteLine("SUCCESS");
            }
            else
            {
                Console.WriteLine(OutlookResponseMessage.ReasonPhrase);
            }

            if (ChunkNumber == ChunkCounter)
            {
                break;
            }

            ChunkCounter++;
        }
    }
}

Perhaps I'm missing something. I only get a SUCCESS message in the first PUT request, the others always give me the error described above. Here's an image of the error with the headers I send. Image

I'd appreciate any help, thanks for reading this far.

EDIT:

Got it working after modifying the the header configuration for the request and changing the way chunks are created.

//Get File Data
byte[] FileByteArray = File.ReadAllBytes(FilePath);

//Create Upload Session
OutlookEndpoint = $"{AppSettings.DriveSettings.OneDriveSettings.Endpoint}/me/drive/items/{FolderId}:/{Name}:/createuploadsession";
OutlookResponseMessage = await OutlookClient.PostAsync(OutlookEndpoint, new StringContent("{}", Encoding.UTF8, "application/json"));
OutlookResponseContent = await OutlookResponseMessage.Content.ReadAsStringAsync();

if (OutlookResponseMessage.IsSuccessStatusCode)
{
    OutlookUpload OutlookUpload = JsonConvert.DeserializeObject<OutlookUpload>(OutlookResponseContent);

    //Check the Created URL
    if (!string.IsNullOrEmpty(OutlookUpload.UploadUrl))
    {
        using MemoryStream FileStream = new MemoryStream(FileByteArray);
        
        //Chunk Calculation
        int ChunkSize = 320 * 1024;
        int ChunkRemaining = 0;
        byte[] ByteBuffer = new byte[ChunkSize];
        int BytesRead = 0;
        
        while ((BytesRead = FileStream.Read(ByteBuffer, 0, ByteBuffer.Length)) > 0)
        {
            if (BytesRead < ChunkSize)
            {
                byte[] LastBuffer = new byte[BytesRead];

                Buffer.BlockCopy(ByteBuffer, 0, LastBuffer, 0, BytesRead);

                ByteBuffer = new byte[BytesRead];

                ByteBuffer = LastBuffer;
            }

            try
            {
                OutlookClient.DefaultRequestHeaders.Clear();

                string UploadEndpoint = OutlookUpload.UploadUrl;

                string BytesHeader = $"bytes {ChunkRemaining}-{ChunkRemaining + ByteBuffer.Length - 1}/{FileByteArray.Length}";

                HttpRequestMessage MicrosoftResponseMessage = new HttpRequestMessage()
                {
                    Content = new ByteArrayContent(ByteBuffer),
                    RequestUri = new Uri(UploadEndpoint),
                    Method = HttpMethod.Put,
                };

                MicrosoftResponseMessage.Content.Headers.Add("Content-Length", ByteBuffer.Length.ToString());

                MicrosoftResponseMessage.Content.Headers.Add("Content-Range", BytesHeader);

                OutlookResponseMessage = await OutlookClient.SendAsync(MicrosoftResponseMessage);
                
                OutlookResponseContent = await OutlookResponseMessage.Content.ReadAsStringAsync();

                if (OutlookResponseMessage.IsSuccessStatusCode)
                {
                    Console.WriteLine("SUCCESS");
                
                    ChunkRemaining += ByteBuffer.Length;
                    
                    if (ChunkRemaining == FileByteArray.Length)
                    {
                        Console.WriteLine("COMPLETED");
                    }
                }
                else
                {
                    Console.WriteLine(OutlookResponseMessage.ReasonPhrase);
                }
            }
            catch (Exception Exception)
            {
                Console.WriteLine(Exception.Message);

                break;
            }
        }
    }
}

Solution

  • Please note that on failures when the client sent a fragment the server had already received, the server will respond with HTTP 416 Requested Range Not Satisfiable. You can request upload status to get a more detailed list of missing ranges. Apparently the content-range and content-length were the problem. You changed the header configuration from the HttpClient to a HttpRequestMessage and it worked perfectly now.