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